feat: 使用DDR读取Hdmi视频流
This commit is contained in:
parent
7e53b805ae
commit
3c73aa344a
|
@ -7,8 +7,16 @@ namespace Peripherals.HdmiInClient;
|
|||
static class HdmiInAddr
|
||||
{
|
||||
public const UInt32 BASE = 0xA000_0000;
|
||||
public const UInt32 HdmiIn_CTRL = BASE + 0x0; //[0]: rstn, 0 is reset.
|
||||
public const UInt32 HdmiIn_READFIFO = BASE + 0x1;
|
||||
|
||||
public const UInt32 CAPTURE_RD_CTRL = BASE + 0x0;
|
||||
|
||||
public const UInt32 START_WR_ADDR0 = BASE + 0x2;
|
||||
public const UInt32 END_WR_ADDR0 = BASE + 0x3;
|
||||
|
||||
public const UInt32 HDMI_NOT_READY = BASE + 0x8;
|
||||
public const UInt32 HDMI_HEIGHT_WIDTH = BASE + 0x9;
|
||||
|
||||
public const UInt32 ADDR_HDMI_WD_START = 0x0400_0000;
|
||||
}
|
||||
|
||||
public class HdmiIn
|
||||
|
@ -21,10 +29,9 @@ public class HdmiIn
|
|||
readonly string address;
|
||||
private IPEndPoint ep;
|
||||
|
||||
// 动态分辨率参数
|
||||
private UInt16 _currentWidth = 960;
|
||||
private UInt16 _currentHeight = 540;
|
||||
private UInt32 _currentFrameLength = 960 * 540 * 2 / 4; // RGB565格式,2字节/像素,按4字节对齐
|
||||
public int Width { get; private set; }
|
||||
public int Height { get; private set; }
|
||||
public int FrameLength => Width * Height * 3 / 4;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化HDMI输入客户端
|
||||
|
@ -44,9 +51,54 @@ public class HdmiIn
|
|||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public async ValueTask<Result<bool>> EnableTrans(bool isEnable)
|
||||
public async ValueTask<Result<bool>> Init(bool enable = true)
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, HdmiInAddr.HdmiIn_CTRL, (isEnable ? 0x00000001u : 0x00000000u));
|
||||
{
|
||||
var ret = await CheckHdmiIsReady();
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to check HDMI ready: {ret.Error}");
|
||||
return new(ret.Error);
|
||||
}
|
||||
if (!ret.Value)
|
||||
{
|
||||
logger.Error("HDMI not ready");
|
||||
return new(false);
|
||||
}
|
||||
}
|
||||
|
||||
int width = -1, height = -1;
|
||||
{
|
||||
var ret = await GetHdmiResolution();
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to get HDMI resolution: {ret.Error}");
|
||||
return new(ret.Error);
|
||||
}
|
||||
(width, height) = ret.Value;
|
||||
}
|
||||
|
||||
{
|
||||
var ret = await ConnectJpeg2Hdmi(width, height);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to connect JPEG to HDMI: {ret.Error}");
|
||||
return new(ret.Error);
|
||||
}
|
||||
if (!ret.Value)
|
||||
{
|
||||
logger.Error("Failed to connect JPEG to HDMI");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (enable) return await SetTransEnable(true);
|
||||
else return true;
|
||||
}
|
||||
|
||||
public async ValueTask<Result<bool>> SetTransEnable(bool isEnable)
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, HdmiInAddr.CAPTURE_RD_CTRL, (isEnable ? 0x00000001u : 0x00000000u));
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to write HdmiIn_CTRL to HdmiIn at {this.address}:{this.port}, error: {ret.Error}");
|
||||
|
@ -75,8 +127,8 @@ public class HdmiIn
|
|||
var result = await UDPClientPool.ReadAddr4BytesAsync(
|
||||
this.ep,
|
||||
this.taskID, // taskID
|
||||
HdmiInAddr.HdmiIn_READFIFO,
|
||||
(int)_currentFrameLength, // 使用当前分辨率的动态大小
|
||||
HdmiInAddr.ADDR_HDMI_WD_START,
|
||||
FrameLength, // 使用当前分辨率的动态大小
|
||||
BurstType.FixedBurst,
|
||||
this.timeout);
|
||||
|
||||
|
@ -99,7 +151,7 @@ public class HdmiIn
|
|||
return result.Value;
|
||||
}
|
||||
|
||||
public async ValueTask<(byte[] header, byte[] data, byte[] footer)?> GetMJpegFrame()
|
||||
public async ValueTask<Optional<(byte[] header, byte[] data, byte[] footer)>> GetMJpegFrame()
|
||||
{
|
||||
// 从HDMI读取RGB24数据
|
||||
var readStartTime = DateTime.UtcNow;
|
||||
|
@ -110,13 +162,13 @@ public class HdmiIn
|
|||
if (!frameResult.IsSuccessful || frameResult.Value == null)
|
||||
{
|
||||
logger.Warn("HDMI帧读取失败或为空");
|
||||
return null;
|
||||
return Optional<(byte[] header, byte[] data, byte[] footer)>.None;
|
||||
}
|
||||
|
||||
var rgb24Data = frameResult.Value;
|
||||
|
||||
// 验证数据长度是否正确 (RGB24为每像素2字节)
|
||||
var expectedLength = _currentWidth * _currentHeight * 2;
|
||||
var expectedLength = Width * Height * 2;
|
||||
if (rgb24Data.Length != expectedLength)
|
||||
{
|
||||
logger.Warn("HDMI数据长度不匹配,期望: {Expected}, 实际: {Actual}",
|
||||
|
@ -125,40 +177,105 @@ public class HdmiIn
|
|||
|
||||
// 将RGB24转换为JPEG(参考Camera版本的处理)
|
||||
var jpegStartTime = DateTime.UtcNow;
|
||||
var jpegResult = Common.Image.ConvertRGB24ToJpeg(rgb24Data, _currentWidth, _currentHeight, 80);
|
||||
var jpegResult = Common.Image.ConvertRGB24ToJpeg(rgb24Data, Width, Height, 80);
|
||||
var jpegEndTime = DateTime.UtcNow;
|
||||
var jpegTime = (jpegEndTime - jpegStartTime).TotalMilliseconds;
|
||||
|
||||
if (!jpegResult.IsSuccessful)
|
||||
{
|
||||
logger.Error("HDMI RGB24转JPEG失败: {Error}", jpegResult.Error);
|
||||
return null;
|
||||
return Optional<(byte[] header, byte[] data, byte[] footer)>.None;
|
||||
}
|
||||
|
||||
var jpegData = jpegResult.Value;
|
||||
|
||||
// 发送MJPEG帧(使用Camera版本的格式)
|
||||
var mjpegFrameHeader = Common.Image.CreateMjpegFrameHeader(jpegData.Length);
|
||||
var mjpegFrameFooter = Common.Image.CreateMjpegFrameFooter();
|
||||
|
||||
return (mjpegFrameHeader, jpegData, mjpegFrameFooter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前分辨率
|
||||
/// </summary>
|
||||
/// <returns>当前分辨率(宽度, 高度)</returns>
|
||||
public (int Width, int Height) GetCurrentResolution()
|
||||
public async ValueTask<Result<bool>> CheckHdmiIsReady()
|
||||
{
|
||||
return (_currentWidth, _currentHeight);
|
||||
var ret = await UDPClientPool.ReadAddrWithWait(
|
||||
this.ep, this.taskID, HdmiInAddr.HDMI_NOT_READY, 0b00, 0b01, 100, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to check HDMI status: {ret.Error}");
|
||||
return new(ret.Error);
|
||||
}
|
||||
return ret.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前帧长度
|
||||
/// </summary>
|
||||
/// <returns>当前帧长度</returns>
|
||||
public UInt32 GetCurrentFrameLength()
|
||||
public async ValueTask<Result<(int, int)>> GetHdmiResolution()
|
||||
{
|
||||
return _currentFrameLength;
|
||||
var ret = await UDPClientPool.ReadAddrByte(
|
||||
this.ep, this.taskID, HdmiInAddr.HDMI_HEIGHT_WIDTH, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to get HDMI resolution: {ret.Error}");
|
||||
return new(ret.Error);
|
||||
}
|
||||
|
||||
var data = ret.Value.Options.Data;
|
||||
if (data == null || data.Length != 4)
|
||||
{
|
||||
logger.Error($"Invalid HDMI resolution data length: {data?.Length ?? 0}");
|
||||
return new(new Exception("Invalid HDMI resolution data length"));
|
||||
}
|
||||
|
||||
var width = data[3] | (data[2] << 8);
|
||||
var height = data[1] | (data[0] << 8);
|
||||
this.Width = width;
|
||||
this.Height = height;
|
||||
|
||||
logger.Info($"HDMI resolution: {width}x{height}");
|
||||
|
||||
return new((width, height));
|
||||
}
|
||||
|
||||
public async ValueTask<Result<bool>> ConnectJpeg2Hdmi(int width, int height)
|
||||
{
|
||||
if (width <= 0 || height <= 0)
|
||||
{
|
||||
logger.Error($"Invalid HDMI resolution: {width}x{height}");
|
||||
return new(new ArgumentException("Invalid HDMI resolution"));
|
||||
}
|
||||
|
||||
var frameSize = (UInt32)(width * height);
|
||||
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddr(
|
||||
this.ep, this.taskID, HdmiInAddr.START_WR_ADDR0, HdmiInAddr.ADDR_HDMI_WD_START, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to set HDMI output start address: {ret.Error}");
|
||||
return new(ret.Error);
|
||||
}
|
||||
if (!ret.Value)
|
||||
{
|
||||
logger.Error($"Failed to set HDMI output start address");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddr(
|
||||
this.ep, this.taskID, HdmiInAddr.END_WR_ADDR0,
|
||||
HdmiInAddr.ADDR_HDMI_WD_START + frameSize - 1, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to set HDMI output end address: {ret.Error}");
|
||||
return new(ret.Error);
|
||||
}
|
||||
if (!ret.Value)
|
||||
{
|
||||
logger.Error($"Failed to set HDMI output address");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ public class HdmiVideoStreamClient
|
|||
{
|
||||
public required HdmiIn HdmiInClient { get; set; }
|
||||
|
||||
public required Jpeg JpegClient { get; set; }
|
||||
// public required Jpeg JpegClient { get; set; }
|
||||
|
||||
public required CancellationTokenSource CTS { get; set; }
|
||||
|
||||
|
@ -102,7 +102,8 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
|||
var client = _clientDict[key];
|
||||
client.CTS.Cancel();
|
||||
|
||||
var disableResult = await client.JpegClient.SetEnable(false);
|
||||
// var disableResult = await client.JpegClient.SetEnable(false);
|
||||
var disableResult = await client.HdmiInClient.SetTransEnable(false);
|
||||
if (disableResult)
|
||||
{
|
||||
logger.Info("Successfully disabled HDMI transmission");
|
||||
|
@ -111,6 +112,8 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
|||
{
|
||||
logger.Error($"Failed to disable HDMI transmission");
|
||||
}
|
||||
|
||||
client.CTS = new CancellationTokenSource();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -120,13 +123,8 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
|||
|
||||
private async Task<HdmiVideoStreamClient?> GetOrCreateClientAsync(string boardId)
|
||||
{
|
||||
if (_clientDict.TryGetValue(boardId, out var client))
|
||||
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));
|
||||
|
@ -141,32 +139,35 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
|||
client = new HdmiVideoStreamClient()
|
||||
{
|
||||
HdmiInClient = new HdmiIn(board.IpAddr, board.Port, 1),
|
||||
JpegClient = new Jpeg(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)
|
||||
var hdmiEnableRet = await client.HdmiInClient.Init(true);
|
||||
if (!hdmiEnableRet.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to enable JPEG transmission for board {boardId}: {jpegEnableRet.Error}");
|
||||
logger.Error($"Failed to enable HDMI transmission for board {boardId}: {hdmiEnableRet.Error}");
|
||||
return null;
|
||||
}
|
||||
logger.Info($"Successfully enabled JPEG transmission for board {boardId}");
|
||||
logger.Info($"Successfully enabled HDMI transmission for board {boardId}");
|
||||
|
||||
client.Width = client.JpegClient.Width;
|
||||
client.Height = client.JpegClient.Height;
|
||||
// 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.HdmiInClient.Width;
|
||||
client.Height = client.HdmiInClient.Height;
|
||||
// client.Width = client.JpegClient.Width;
|
||||
// client.Height = client.JpegClient.Height;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -195,15 +196,16 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
|||
return;
|
||||
}
|
||||
|
||||
var hdmiInToken = _clientDict[boardId].CTS.Token;
|
||||
var token = CancellationTokenSource.CreateLinkedTokenSource(
|
||||
cancellationToken, client.CTS.Token).Token;
|
||||
|
||||
if (path == "/snapshot")
|
||||
{
|
||||
await HandleSnapshotRequestAsync(context.Response, client, hdmiInToken);
|
||||
await HandleSnapshotRequestAsync(context.Response, client, token);
|
||||
}
|
||||
else if (path == "/mjpeg")
|
||||
{
|
||||
await HandleMjpegStreamAsync(context.Response, client, hdmiInToken);
|
||||
await HandleMjpegStreamAsync(context.Response, client, token);
|
||||
}
|
||||
else if (path == "/video")
|
||||
{
|
||||
|
@ -223,36 +225,47 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
|||
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 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 jpegData = frameResult.Value[0];
|
||||
|
||||
var quantTableResult = await client.JpegClient.GetQuantizationTable();
|
||||
if (!quantTableResult.IsSuccessful || quantTableResult.Value == null)
|
||||
{
|
||||
logger.Error("获取JPEG量化表失败: {Error}", quantTableResult.Error);
|
||||
response.StatusCode = 500;
|
||||
var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to get quantization table");
|
||||
await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken);
|
||||
response.Close();
|
||||
return;
|
||||
}
|
||||
// var quantTableResult = await client.JpegClient.GetQuantizationTable();
|
||||
// if (!quantTableResult.IsSuccessful || quantTableResult.Value == null)
|
||||
// {
|
||||
// logger.Error("获取JPEG量化表失败: {Error}", quantTableResult.Error);
|
||||
// response.StatusCode = 500;
|
||||
// var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to get quantization table");
|
||||
// await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken);
|
||||
// response.Close();
|
||||
// return;
|
||||
// }
|
||||
|
||||
var jpegImage = Common.Image.CompleteJpegData(jpegData, client.Width, client.Height, quantTableResult.Value);
|
||||
if (!jpegImage.IsSuccessful)
|
||||
// var jpegImage = Common.Image.CompleteJpegData(jpegData, client.Width, client.Height, quantTableResult.Value);
|
||||
// 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;
|
||||
// }
|
||||
|
||||
var jpegImage = await client.HdmiInClient.GetMJpegFrame();
|
||||
if (!jpegImage.HasValue)
|
||||
{
|
||||
logger.Error("JPEG数据补全失败");
|
||||
logger.Error("获取HDMI MJPEG帧失败");
|
||||
response.StatusCode = 500;
|
||||
var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to complete JPEG data");
|
||||
var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to get HDMI MJPEG frame");
|
||||
await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken);
|
||||
response.Close();
|
||||
return;
|
||||
|
@ -260,13 +273,13 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
|||
|
||||
// 设置响应头(参考Camera版本)
|
||||
response.ContentType = "image/jpeg";
|
||||
response.ContentLength64 = jpegImage.Value.Length;
|
||||
response.ContentLength64 = jpegImage.Value.data.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.WriteAsync(jpegImage.Value.data, 0, jpegImage.Value.data.Length, cancellationToken);
|
||||
await response.OutputStream.FlushAsync(cancellationToken);
|
||||
|
||||
logger.Debug("已发送HDMI快照图像,大小:{Size} 字节", jpegImage.Value.Length);
|
||||
logger.Debug("已发送HDMI快照图像,大小:{Size} 字节", jpegImage.Value.data.Length);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -275,6 +288,7 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
|||
}
|
||||
finally
|
||||
{
|
||||
response.StatusCode = 200;
|
||||
response.Close();
|
||||
}
|
||||
}
|
||||
|
@ -292,17 +306,17 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
|||
|
||||
logger.Debug("开始HDMI MJPEG流传输");
|
||||
|
||||
var quantTableResult = await client.JpegClient.GetQuantizationTable();
|
||||
if (!quantTableResult.IsSuccessful || quantTableResult.Value == null)
|
||||
{
|
||||
logger.Error("获取JPEG量化表失败: {Error}", quantTableResult.Error);
|
||||
response.StatusCode = 500;
|
||||
await response.OutputStream.WriteAsync(
|
||||
System.Text.Encoding.UTF8.GetBytes("Failed to get quantization table"), 0, 0, cancellationToken);
|
||||
response.Close();
|
||||
return;
|
||||
}
|
||||
var quantTable = quantTableResult.Value;
|
||||
// var quantTableResult = await client.JpegClient.GetQuantizationTable();
|
||||
// if (!quantTableResult.IsSuccessful || quantTableResult.Value == null)
|
||||
// {
|
||||
// logger.Error("获取JPEG量化表失败: {Error}", quantTableResult.Error);
|
||||
// response.StatusCode = 500;
|
||||
// await response.OutputStream.WriteAsync(
|
||||
// System.Text.Encoding.UTF8.GetBytes("Failed to get quantization table"), 0, 0, cancellationToken);
|
||||
// response.Close();
|
||||
// return;
|
||||
// }
|
||||
// var quantTable = quantTableResult.Value;
|
||||
|
||||
int frameCounter = 0;
|
||||
|
||||
|
@ -310,30 +324,10 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
|||
{
|
||||
var frameStartTime = DateTime.UtcNow;
|
||||
|
||||
var frameResult =
|
||||
await client.JpegClient.GetMultiFrames((uint)client.Offset);
|
||||
if (!frameResult.IsSuccessful || frameResult.Value == null || frameResult.Value.Count == 0)
|
||||
var frameRet = await client.HdmiInClient.GetMJpegFrame();
|
||||
if (!frameRet.HasValue)
|
||||
{
|
||||
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, quantTable);
|
||||
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;
|
||||
|
@ -354,7 +348,50 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
|||
frameCounter, totalTime, frame.data.Length);
|
||||
}
|
||||
|
||||
}
|
||||
// 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, quantTable);
|
||||
// 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)
|
||||
|
@ -366,7 +403,7 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
|||
try
|
||||
{
|
||||
// 停止传输时禁用HDMI传输
|
||||
await client.HdmiInClient.EnableTrans(false);
|
||||
await client.HdmiInClient.SetTransEnable(false);
|
||||
logger.Info("已禁用HDMI传输");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
@ -81,7 +81,12 @@
|
|||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { AuthManager } from "@/utils/AuthManager";
|
||||
import { ExamClient, ResourceClient, type ExamInfo } from "@/APIClient";
|
||||
import {
|
||||
ExamClient,
|
||||
ResourceClient,
|
||||
ResourcePurpose,
|
||||
type ExamInfo,
|
||||
} from "@/APIClient";
|
||||
|
||||
// 接口定义
|
||||
interface Tutorial {
|
||||
|
@ -146,7 +151,7 @@ onMounted(async () => {
|
|||
const resourceList = await resourceClient.getResourceList(
|
||||
exam.id,
|
||||
"cover",
|
||||
"template",
|
||||
ResourcePurpose.Template,
|
||||
);
|
||||
if (resourceList && resourceList.length > 0) {
|
||||
// 使用第一个封面资源
|
||||
|
|
|
@ -430,13 +430,13 @@ function startStream() {
|
|||
}
|
||||
|
||||
// 停止播放视频流
|
||||
function stopStream() {
|
||||
async function stopStream() {
|
||||
isPlaying.value = false;
|
||||
currentVideoSource.value = "";
|
||||
videoStatus.value = "已停止播放";
|
||||
|
||||
const client = AuthManager.createClient(HdmiVideoStreamClient);
|
||||
client.disableHdmiTransmission();
|
||||
await client.disableHdmiTransmission();
|
||||
|
||||
addLog("info", "停止播放HDMI视频流");
|
||||
alert?.info("已停止播放HDMI视频流");
|
||||
|
@ -467,8 +467,9 @@ function handleVideoClick() {
|
|||
}
|
||||
|
||||
// 重试连接
|
||||
function tryReconnect() {
|
||||
async function tryReconnect() {
|
||||
hasVideoError.value = false;
|
||||
await stopStream();
|
||||
if (endpoint.value) {
|
||||
startStream();
|
||||
}
|
||||
|
|
|
@ -171,7 +171,12 @@ import { useProvideComponentManager } from "@/components/LabCanvas";
|
|||
import { useAlertStore } from "@/components/Alert";
|
||||
import { AuthManager } from "@/utils/AuthManager";
|
||||
import { useEquipments } from "@/stores/equipments";
|
||||
import { DataClient, ResourceClient, type Board } from "@/APIClient";
|
||||
import {
|
||||
DataClient,
|
||||
ResourceClient,
|
||||
ResourcePurpose,
|
||||
type Board,
|
||||
} from "@/APIClient";
|
||||
|
||||
import { useRoute } from "vue-router";
|
||||
const route = useRoute();
|
||||
|
@ -257,7 +262,11 @@ async function loadDocumentContent() {
|
|||
const client = AuthManager.createClient(ResourceClient);
|
||||
|
||||
// 获取markdown类型的模板资源列表
|
||||
const resources = await client.getResourceList(examId, "doc", "template");
|
||||
const resources = await client.getResourceList(
|
||||
examId,
|
||||
"doc",
|
||||
ResourcePurpose.Template,
|
||||
);
|
||||
|
||||
if (resources && resources.length > 0) {
|
||||
// 获取第一个markdown资源
|
||||
|
|
Loading…
Reference in New Issue