using System.Net;
using DotNext;
using WebProtocol;
namespace Peripherals.HdmiInClient;
static class HdmiInAddr
{
public const UInt32 BASE = 0xA000_0000;
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
{
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
readonly int timeout = 500;
readonly int taskID;
readonly int port;
readonly string address;
private IPEndPoint ep;
public int Width { get; private set; }
public int Height { get; private set; }
public int FrameLength => Width * Height * 3 / 4;
///
/// 初始化HDMI输入客户端
///
/// HDMI输入设备IP地址
/// HDMI输入设备端口
/// 任务ID
/// 超时时间(毫秒)
public HdmiIn(string address, int port, int taskID, int timeout = 500)
{
if (timeout < 0)
throw new ArgumentException("Timeout couldn't be negative", nameof(timeout));
this.address = address;
this.port = port;
this.ep = new IPEndPoint(IPAddress.Parse(address), port);
this.taskID = taskID;
this.timeout = timeout;
}
public async ValueTask> Init(bool enable = true)
{
{
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> 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}");
return new(ret.Error);
}
if (!ret.Value)
{
logger.Error($"HdmiIn_CTRL write returned false for HdmiIn at {this.address}:{this.port}");
return false;
}
return true;
}
///
/// 读取一帧图像数据
///
/// 包含图像数据的字节数组
public async ValueTask> ReadFrame()
{
// 只在第一次或出错时清除UDP缓冲区,避免每帧都清除造成延迟
// MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
logger.Trace($"Reading frame from HdmiIn {this.address}");
// 使用UDPClientPool读取图像帧数据
var result = await UDPClientPool.ReadAddr4BytesAsync(
this.ep,
this.taskID, // taskID
HdmiInAddr.ADDR_HDMI_WD_START,
FrameLength, // 使用当前分辨率的动态大小
BurstType.FixedBurst,
this.timeout);
if (!result.IsSuccessful)
{
logger.Error($"Failed to read frame from HdmiIn {this.address}:{this.port}, error: {result.Error}");
// 读取失败时清除缓冲区,为下次读取做准备
try
{
MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
}
catch (Exception ex)
{
logger.Warn($"Failed to clear UDP data after read error: {ex.Message}");
}
return new(result.Error);
}
logger.Trace($"Successfully read frame from HdmiIn {this.address}:{this.port}, data length: {result.Value.Length} bytes");
return result.Value;
}
public async ValueTask> 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 Optional<(byte[] header, byte[] data, byte[] footer)>.None;
}
var rgb24Data = frameResult.Value;
// 验证数据长度是否正确 (RGB24为每像素2字节)
var expectedLength = Width * Height * 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, Width, Height, 80);
var jpegEndTime = DateTime.UtcNow;
var jpegTime = (jpegEndTime - jpegStartTime).TotalMilliseconds;
if (!jpegResult.IsSuccessful)
{
logger.Error("HDMI RGB24转JPEG失败: {Error}", jpegResult.Error);
return Optional<(byte[] header, byte[] data, byte[] footer)>.None;
}
var jpegData = jpegResult.Value;
var mjpegFrameHeader = Common.Image.CreateMjpegFrameHeader(jpegData.Length);
var mjpegFrameFooter = Common.Image.CreateMjpegFrameFooter();
return (mjpegFrameHeader, jpegData, mjpegFrameFooter);
}
public async ValueTask> CheckHdmiIsReady()
{
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;
}
public async ValueTask> GetHdmiResolution()
{
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> 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;
}
}