feat&fix: 完善JPEGClient逻辑,前端新增编码器组件
This commit is contained in:
@@ -258,12 +258,12 @@ public class Image
|
||||
/// <summary>
|
||||
/// 将原始 JPEG 数据补全 JPEG 头部,生成完整的 JPEG 图片
|
||||
/// </summary>
|
||||
/// <param name="jpegData">原始 JPEG 数据(可能缺少完整头部)</param>
|
||||
/// <param name="jpegData">原始 JPEG 扫描数据(不含头尾)</param>
|
||||
/// <param name="width">图像宽度</param>
|
||||
/// <param name="height">图像高度</param>
|
||||
/// <param name="quality">JPEG质量(1-100,默认80)</param>
|
||||
/// <param name="quantizationTable">量化表数组(Y0-Y63, Cb0-Cb63, Cr0-Cr63,共192个值)</param>
|
||||
/// <returns>完整的 JPEG 图片数据</returns>
|
||||
public static Result<byte[]> CompleteJpegData(byte[] jpegData, int width, int height, int quality = 80)
|
||||
public static Result<byte[]> CompleteJpegData(byte[] jpegData, int width, int height, uint[] quantizationTable)
|
||||
{
|
||||
if (jpegData == null)
|
||||
return new(new ArgumentNullException(nameof(jpegData)));
|
||||
@@ -271,95 +271,148 @@ public class Image
|
||||
if (width <= 0 || height <= 0)
|
||||
return new(new ArgumentException("Width and height must be positive"));
|
||||
|
||||
if (quality < 1 || quality > 100)
|
||||
return new(new ArgumentException("Quality must be between 1 and 100"));
|
||||
if (quantizationTable == null || quantizationTable.Length != 192)
|
||||
return new(new ArgumentException("Quantization table must contain exactly 192 values (64 Y + 64 Cb + 64 Cr)"));
|
||||
|
||||
try
|
||||
{
|
||||
// 检查是否已经是完整的 JPEG 文件(以 FFD8 开头,FFD9 结尾)
|
||||
if (jpegData.Length >= 4 &&
|
||||
jpegData[0] == 0xFF && jpegData[1] == 0xD8 &&
|
||||
jpegData[jpegData.Length - 2] == 0xFF && jpegData[jpegData.Length - 1] == 0xD9)
|
||||
var jpegBytes = new List<byte>();
|
||||
|
||||
// SOI (Start of Image)
|
||||
jpegBytes.AddRange(new byte[] { 0xFF, 0xD8 });
|
||||
|
||||
// APP0 段
|
||||
jpegBytes.AddRange(new byte[] {
|
||||
0xFF, 0xE0, // APP0 marker
|
||||
0x00, 0x10, // Length (16 bytes)
|
||||
0x4A, 0x46, 0x49, 0x46, 0x00, // "JFIF\0"
|
||||
0x01, 0x01, // Version 1.1
|
||||
0x00, // Units: 0 = no units
|
||||
0x00, 0x01, // X density (1)
|
||||
0x00, 0x01, // Y density (1)
|
||||
0x00, // Thumbnail width
|
||||
0x00 // Thumbnail height
|
||||
});
|
||||
|
||||
// DQT (Define Quantization Table) - Y table
|
||||
jpegBytes.AddRange(new byte[] { 0xFF, 0xDB }); // DQT marker
|
||||
jpegBytes.AddRange(new byte[] { 0x00, 0x43 }); // Length (67 bytes)
|
||||
jpegBytes.Add(0x00); // Table ID (0 = Y table)
|
||||
|
||||
// 添加Y量化表 (quantizationTable[0-63])
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
// 已经是完整的 JPEG 文件,直接返回
|
||||
return jpegData;
|
||||
jpegBytes.Add((byte)Math.Min(255, quantizationTable[i]));
|
||||
}
|
||||
|
||||
// 创建一个临时的 RGB24 图像用于生成 JPEG 头部
|
||||
using var tempImage = new SixLabors.ImageSharp.Image<Rgb24>(new Configuration
|
||||
// DQT (Define Quantization Table) - CbCr table
|
||||
jpegBytes.AddRange(new byte[] { 0xFF, 0xDB }); // DQT marker
|
||||
jpegBytes.AddRange(new byte[] { 0x00, 0x43 }); // Length (67 bytes)
|
||||
jpegBytes.Add(0x01); // Table ID (1 = CbCr table)
|
||||
|
||||
// 添加Cb量化表 (quantizationTable[64-127]),但这里使用Cr表的数据作为CbCr共用
|
||||
for (int i = 128; i < 192; i++) // 使用Cr量化表 (quantizationTable[128-191])
|
||||
{
|
||||
|
||||
}, width, height);
|
||||
|
||||
// 填充临时图像(使用简单的渐变色作为占位符)
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
tempImage[x, y] = new Rgb24((byte)(x % 256), (byte)(y % 256), 128);
|
||||
}
|
||||
jpegBytes.Add((byte)Math.Min(255, quantizationTable[i]));
|
||||
}
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
tempImage.SaveAsJpeg(stream, new JpegEncoder { Quality = quality });
|
||||
var completeJpeg = stream.ToArray();
|
||||
// SOF0 (Start of Frame)
|
||||
jpegBytes.AddRange(new byte[] {
|
||||
0xFF, 0xC0, // SOF0 marker
|
||||
0x00, 0x11, // Length (17 bytes)
|
||||
0x08, // Precision (8 bits)
|
||||
(byte)((height >> 8) & 0xFF), (byte)(height & 0xFF), // Height
|
||||
(byte)((width >> 8) & 0xFF), (byte)(width & 0xFF), // Width
|
||||
0x03, // Number of components
|
||||
0x01, 0x11, 0x00, // Y component
|
||||
0x02, 0x11, 0x01, // Cb component
|
||||
0x03, 0x11, 0x01 // Cr component
|
||||
});
|
||||
|
||||
// 如果原始数据看起来是 JPEG 扫描数据,尝试替换扫描数据部分
|
||||
if (jpegData.Length > 0)
|
||||
{
|
||||
// 查找 JPEG 扫描数据开始位置(SOS 标记 0xFFDA 后)
|
||||
int sosIndex = -1;
|
||||
for (int i = 0; i < completeJpeg.Length - 1; i++)
|
||||
{
|
||||
if (completeJpeg[i] == 0xFF && completeJpeg[i + 1] == 0xDA)
|
||||
{
|
||||
// 跳过 SOS 段头部,找到实际扫描数据开始位置
|
||||
i += 2; // 跳过 FF DA
|
||||
if (i < completeJpeg.Length - 1)
|
||||
{
|
||||
int segmentLength = (completeJpeg[i] << 8) | completeJpeg[i + 1];
|
||||
sosIndex = i + segmentLength;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// DHT (Define Huffman Table) - DC Y table
|
||||
jpegBytes.AddRange(new byte[] {
|
||||
0xFF, 0xC4, // DHT marker
|
||||
0x00, 0x1F, // Length
|
||||
0x00, // Table class and ID (DC table 0)
|
||||
// DC Y Huffman table
|
||||
0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B
|
||||
});
|
||||
|
||||
// 查找 EOI 标记位置(0xFFD9)
|
||||
int eoiIndex = -1;
|
||||
for (int i = completeJpeg.Length - 2; i >= 0; i--)
|
||||
{
|
||||
if (completeJpeg[i] == 0xFF && completeJpeg[i + 1] == 0xD9)
|
||||
{
|
||||
eoiIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// DHT (Define Huffman Table) - AC Y table (简化版)
|
||||
jpegBytes.AddRange(new byte[] {
|
||||
0xFF, 0xC4, // DHT marker
|
||||
0x00, 0xB5, // Length
|
||||
0x10 // Table class and ID (AC table 0)
|
||||
});
|
||||
|
||||
// AC Y Huffman table数据
|
||||
jpegBytes.AddRange(new byte[] {
|
||||
0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D,
|
||||
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
|
||||
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0,
|
||||
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28,
|
||||
0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
|
||||
0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
|
||||
0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
|
||||
0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
|
||||
0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5,
|
||||
0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2,
|
||||
0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
|
||||
0xF9, 0xFA
|
||||
});
|
||||
|
||||
if (sosIndex > 0 && eoiIndex > sosIndex)
|
||||
{
|
||||
// 替换扫描数据部分
|
||||
var headerLength = sosIndex;
|
||||
var footerStart = eoiIndex;
|
||||
var footerLength = completeJpeg.Length - footerStart;
|
||||
// DHT (Define Huffman Table) - DC CbCr table
|
||||
jpegBytes.AddRange(new byte[] {
|
||||
0xFF, 0xC4, // DHT marker
|
||||
0x00, 0x1F, // Length
|
||||
0x01, // Table class and ID (DC table 1)
|
||||
// DC CbCr Huffman table
|
||||
0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B
|
||||
});
|
||||
|
||||
var newJpegLength = headerLength + jpegData.Length + footerLength;
|
||||
var newJpegData = new byte[newJpegLength];
|
||||
// DHT (Define Huffman Table) - AC CbCr table
|
||||
jpegBytes.AddRange(new byte[] {
|
||||
0xFF, 0xC4, // DHT marker
|
||||
0x00, 0xB5, // Length
|
||||
0x11 // Table class and ID (AC table 1)
|
||||
});
|
||||
|
||||
// AC CbCr Huffman table数据(与AC Y table相同)
|
||||
jpegBytes.AddRange(new byte[] {
|
||||
0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D,
|
||||
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
|
||||
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0,
|
||||
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28,
|
||||
0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
|
||||
0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
|
||||
0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
|
||||
0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
|
||||
0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5,
|
||||
0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2,
|
||||
0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
|
||||
0xF9, 0xFA
|
||||
});
|
||||
|
||||
// 复制头部
|
||||
Array.Copy(completeJpeg, 0, newJpegData, 0, headerLength);
|
||||
// SOS (Start of Scan)
|
||||
jpegBytes.AddRange(new byte[] {
|
||||
0xFF, 0xDA, // SOS marker
|
||||
0x00, 0x0C, // Length (12 bytes)
|
||||
0x03, // Number of components
|
||||
0x01, 0x00, // Y component, DC/AC table
|
||||
0x02, 0x11, // Cb component, DC/AC table
|
||||
0x03, 0x11, // Cr component, DC/AC table
|
||||
0x00, 0x3F, 0x00 // Start of spectral, End of spectral, Ah/Al
|
||||
});
|
||||
|
||||
// 复制原始扫描数据
|
||||
Array.Copy(jpegData, 0, newJpegData, headerLength, jpegData.Length);
|
||||
// 添加原始 JPEG 扫描数据
|
||||
jpegBytes.AddRange(jpegData);
|
||||
|
||||
// 复制尾部
|
||||
Array.Copy(completeJpeg, footerStart, newJpegData, headerLength + jpegData.Length, footerLength);
|
||||
// EOI (End of Image)
|
||||
jpegBytes.AddRange(new byte[] { 0xFF, 0xD9 });
|
||||
|
||||
return newJpegData;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果无法智能合并,返回完整的模板 JPEG
|
||||
return completeJpeg;
|
||||
return jpegBytes.ToArray();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Peripherals.JpegClient;
|
||||
|
||||
static class JpegAddr
|
||||
{
|
||||
const UInt32 BASE = 0x0000_0000;
|
||||
const UInt32 BASE = 0xA000_0000;
|
||||
|
||||
public const UInt32 CAPTURE_RD_CTRL = BASE + 0x0;
|
||||
public const UInt32 CAPTURE_WR_CTRL = BASE + 0x1;
|
||||
@@ -26,9 +26,11 @@ static class JpegAddr
|
||||
public const UInt32 JPEG_FRAME_SAVE_NUM = BASE + 0xC;
|
||||
public const UInt32 JPEG_FIFO_FRAME_INFO = BASE + 0xD;
|
||||
|
||||
public const UInt32 ADDR_HDMI_WD_START = 0x4000_0000;
|
||||
public const UInt32 ADDR_JPEG_START = 0x8000_0000;
|
||||
public const UInt32 ADDR_JPEG_END = 0xA000_0000;
|
||||
public const UInt32 JPEG_QUANTIZATION_TABLE = BASE + 0x100;
|
||||
|
||||
public const UInt32 ADDR_HDMI_WD_START = 0x0400_0000;
|
||||
public const UInt32 ADDR_JPEG_START = 0x0800_0000;
|
||||
public const UInt32 ADDR_JPEG_END = 0x09FF_FFFF;
|
||||
}
|
||||
|
||||
public class JpegInfo
|
||||
@@ -142,23 +144,33 @@ public class Jpeg
|
||||
else return true;
|
||||
}
|
||||
|
||||
public async ValueTask<bool> SetEnable(bool enable)
|
||||
public async ValueTask<Result<bool>> SetEnable(bool enable)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddrSeq(
|
||||
this.ep,
|
||||
this.taskID,
|
||||
[JpegAddr.CAPTURE_RD_CTRL, JpegAddr.CAPTURE_WR_CTRL],
|
||||
[0b11, 0b01],
|
||||
this.timeout
|
||||
);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to set JPEG enable: {ret.Error}");
|
||||
return false;
|
||||
var ret = await UDPClientPool.WriteAddrSeq(
|
||||
this.ep,
|
||||
this.taskID,
|
||||
[JpegAddr.CAPTURE_RD_CTRL, JpegAddr.CAPTURE_WR_CTRL],
|
||||
[0b11, 0b01],
|
||||
this.timeout
|
||||
);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to set JPEG enable: {ret.Error}");
|
||||
return ret.Value;
|
||||
}
|
||||
}
|
||||
return ret.Value;
|
||||
{
|
||||
var ret = await AddFrameNum2Process(1);
|
||||
if (!ret)
|
||||
{
|
||||
logger.Error($"Failed to AddFrameNum2Process: {ret}");
|
||||
return ret.Value;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -181,7 +193,7 @@ public class Jpeg
|
||||
public async ValueTask<Result<bool>> CheckHdmiIsReady()
|
||||
{
|
||||
var ret = await UDPClientPool.ReadAddrWithWait(
|
||||
this.ep, this.taskID, JpegAddr.HDMI_NOT_READY, 0b01, 0b01, 100, this.timeout);
|
||||
this.ep, this.taskID, JpegAddr.HDMI_NOT_READY, 0b00, 0b01, 100, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to check HDMI status: {ret.Error}");
|
||||
@@ -192,8 +204,8 @@ public class Jpeg
|
||||
|
||||
public async ValueTask<Result<(int, int)>> GetHdmiResolution()
|
||||
{
|
||||
var ret = await UDPClientPool.ReadAddr(
|
||||
this.ep, this.taskID, JpegAddr.HDMI_HEIGHT_WIDTH, 0, this.timeout);
|
||||
var ret = await UDPClientPool.ReadAddrByte(
|
||||
this.ep, this.taskID, JpegAddr.HDMI_HEIGHT_WIDTH, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to get HDMI resolution: {ret.Error}");
|
||||
@@ -207,11 +219,13 @@ public class Jpeg
|
||||
return new(new Exception("Invalid HDMI resolution data length"));
|
||||
}
|
||||
|
||||
var width = data[0] | (data[1] << 8);
|
||||
var height = data[2] | (data[3] << 8);
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -223,7 +237,22 @@ public class Jpeg
|
||||
return new(new ArgumentException("Invalid HDMI resolution"));
|
||||
}
|
||||
|
||||
var frameSize = (UInt32)(width * height / 4);
|
||||
var frameSize = (UInt32)(width * height);
|
||||
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddr(
|
||||
this.ep, this.taskID, JpegAddr.JPEG_HEIGHT_WIDTH, (uint)((height << 16) + width), 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(
|
||||
@@ -243,7 +272,7 @@ public class Jpeg
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddr(
|
||||
this.ep, this.taskID, JpegAddr.END_WR_ADDR0,
|
||||
JpegAddr.ADDR_HDMI_WD_START + frameSize, this.timeout);
|
||||
JpegAddr.ADDR_HDMI_WD_START + frameSize - 1, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to set HDMI output end address: {ret.Error}");
|
||||
@@ -274,7 +303,7 @@ public class Jpeg
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddr(
|
||||
this.ep, this.taskID, JpegAddr.END_RD_ADDR0,
|
||||
JpegAddr.ADDR_HDMI_WD_START + frameSize, this.timeout);
|
||||
JpegAddr.ADDR_HDMI_WD_START + frameSize - 1, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to set jpeg input end address: {ret.Error}");
|
||||
@@ -339,14 +368,39 @@ public class Jpeg
|
||||
|
||||
public async ValueTask<uint> GetFrameNumber()
|
||||
{
|
||||
var ret = await UDPClientPool.ReadAddrByte(
|
||||
this.ep, this.taskID, JpegAddr.JPEG_FRAME_SAVE_NUM, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
const int maxAttempts = 10;
|
||||
const int delayMs = 5;
|
||||
|
||||
for (int attempt = 0; attempt < maxAttempts; attempt++)
|
||||
{
|
||||
logger.Error($"Failed to get JPEG frame number: {ret.Error}");
|
||||
return 0;
|
||||
var ret = await UDPClientPool.ReadAddrByte(
|
||||
this.ep, this.taskID, JpegAddr.JPEG_FRAME_SAVE_NUM, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to get JPEG frame number on attempt {attempt + 1}: {ret.Error}");
|
||||
if (attempt < maxAttempts - 1)
|
||||
{
|
||||
await Task.Delay(delayMs);
|
||||
continue;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
var frameNumber = Number.BytesToUInt32(ret.Value.Options.Data ?? Array.Empty<byte>()).Value;
|
||||
if (frameNumber != 0)
|
||||
{
|
||||
return frameNumber;
|
||||
}
|
||||
|
||||
// 如果不是最后一次尝试,等待5ms后重试
|
||||
if (attempt < maxAttempts - 1)
|
||||
{
|
||||
await Task.Delay(delayMs);
|
||||
}
|
||||
}
|
||||
return Number.BytesToUInt32(ret.Value.Options.Data ?? Array.Empty<byte>()).Value;
|
||||
|
||||
// 所有尝试都失败或返回0
|
||||
return 0;
|
||||
}
|
||||
|
||||
public async ValueTask<Optional<List<JpegInfo>>> GetFrameInfo(int num)
|
||||
@@ -379,14 +433,14 @@ public class Jpeg
|
||||
return new(infos);
|
||||
}
|
||||
|
||||
public async ValueTask<bool> AddFrameNum2Process(uint cnt)
|
||||
public async ValueTask<Result<bool>> AddFrameNum2Process(uint cnt)
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddr(
|
||||
this.ep, this.taskID, JpegAddr.JPEG_ADD_NEED_FRAME_NUM, cnt, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to update pointer: {ret.Error}");
|
||||
return false;
|
||||
return ret.Value;
|
||||
}
|
||||
return ret.Value;
|
||||
}
|
||||
@@ -510,4 +564,45 @@ public class Jpeg
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
public async ValueTask<Result<uint[]?>> GetQuantizationTable()
|
||||
{
|
||||
const int totalQuantValues = 8 * 8 * 3; // Y(64) + Cb(64) + Cr(64) = 192个量化值
|
||||
const int bytesPerValue = 4; // 每个量化值32bit = 4字节
|
||||
const int totalBytes = totalQuantValues * bytesPerValue; // 总共768字节
|
||||
|
||||
try
|
||||
{
|
||||
var ret = await UDPClientPool.ReadAddr4Bytes(
|
||||
this.ep, this.taskID, JpegAddr.JPEG_QUANTIZATION_TABLE, totalBytes, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to read JPEG quantization table: {ret.Error}");
|
||||
return new(ret.Error);
|
||||
}
|
||||
|
||||
var data = ret.Value;
|
||||
if (data == null || data.Length != totalBytes)
|
||||
{
|
||||
logger.Error($"Invalid quantization table data length: expected {totalBytes}, got {data?.Length ?? 0}");
|
||||
return new(new Exception("Invalid quantization table data length"));
|
||||
}
|
||||
|
||||
var quantTable = new uint[totalQuantValues];
|
||||
for (int i = 0; i < totalQuantValues; i++)
|
||||
{
|
||||
// 每32bit为一个量化值,按小端序读取
|
||||
var offset = i * bytesPerValue;
|
||||
quantTable[i] = (uint)(data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24));
|
||||
}
|
||||
|
||||
logger.Debug($"Successfully read JPEG quantization table with {totalQuantValues} values");
|
||||
return quantTable;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex, "Exception occurred while reading JPEG quantization table");
|
||||
return new(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +235,19 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
||||
}
|
||||
|
||||
var jpegData = frameResult.Value[0];
|
||||
var jpegImage = Common.Image.CompleteJpegData(jpegData, client.Width, client.Height);
|
||||
|
||||
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)
|
||||
{
|
||||
logger.Error("JPEG数据补全失败");
|
||||
@@ -280,6 +292,18 @@ 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;
|
||||
|
||||
int frameCounter = 0;
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
@@ -297,7 +321,7 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
||||
|
||||
foreach (var framebytes in frameResult.Value)
|
||||
{
|
||||
var jpegImage = Common.Image.CompleteJpegData(framebytes, client.Width, client.Height);
|
||||
var jpegImage = Common.Image.CompleteJpegData(framebytes, client.Width, client.Height, quantTable);
|
||||
if (!jpegImage.IsSuccessful)
|
||||
{
|
||||
logger.Error("JPEG数据不完整");
|
||||
|
||||
Reference in New Issue
Block a user