using System;
using System.Collections.Generic;
using System.Text;
namespace ivf_tl_Operate.Debug
{
///
/// MJPEG multipart 流切帧状态机(纯逻辑)。喂入任意大小的字节块,吐出完整 JPEG 帧。
/// 处理:一帧跨多个块、一个块含多帧、半个头跨块。只认 Content-Length 截帧(不靠扫 boundary 找帧尾,稳)。
/// 流格式:--frame\r\nContent-Type: image/jpeg\r\nContent-Length: N\r\n\r\n[N 字节 JPEG]\r\n
///
public sealed class MjpegFrameParser
{
private readonly List _buf = new List();
private int _expectedLen = -1; // -1 = 还没解析到帧头;>=0 = 正在收帧体
private int _bodyStart = -1;
/// 喂入一段字节,返回这次能切出的完整 JPEG 帧(可能 0~多帧)。
public IEnumerable Feed(byte[] chunk, int count)
{
if (chunk == null || count <= 0) return Array.Empty();
if (count == chunk.Length) _buf.AddRange(chunk);
else for (int i = 0; i < count; i++) _buf.Add(chunk[i]);
var frames = new List();
while (true)
{
if (_expectedLen < 0)
{
// 找头部结束标志 \r\n\r\n
int sep = IndexOfDoubleCrlf(_buf);
if (sep < 0) break; // 头还没收全,等下一块
string header = Encoding.ASCII.GetString(_buf.ToArray(), 0, sep);
int len = ParseContentLength(header);
if (len < 0)
{
// 头里没 Content-Length(异常/坏帧)→ 丢弃到分隔符后,继续
_buf.RemoveRange(0, sep + 4);
continue;
}
_expectedLen = len;
_bodyStart = sep + 4; // \r\n\r\n 之后是帧体
}
// 收帧体:需要 _bodyStart + _expectedLen 字节
if (_buf.Count < _bodyStart + _expectedLen) break; // 帧体没收全,等下一块
var jpeg = new byte[_expectedLen];
_buf.CopyTo(_bodyStart, jpeg, 0, _expectedLen);
frames.Add(jpeg);
// 移除已消费的帧(头 + 体 + 可能的尾 \r\n)。下一帧从 --frame 开始,统一靠下轮找 \r\n\r\n。
int consumed = _bodyStart + _expectedLen;
// 跳过帧体后的 \r\n(若存在)
if (_buf.Count >= consumed + 2 && _buf[consumed] == (byte)'\r' && _buf[consumed + 1] == (byte)'\n')
consumed += 2;
_buf.RemoveRange(0, consumed);
_expectedLen = -1; _bodyStart = -1;
}
return frames;
}
private static int IndexOfDoubleCrlf(List buf)
{
for (int i = 0; i + 3 < buf.Count; i++)
if (buf[i] == (byte)'\r' && buf[i + 1] == (byte)'\n' && buf[i + 2] == (byte)'\r' && buf[i + 3] == (byte)'\n')
return i;
return -1;
}
private static int ParseContentLength(string header)
{
foreach (var line in header.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries))
{
int colon = line.IndexOf(':');
if (colon < 0) continue;
if (line.Substring(0, colon).Trim().Equals("Content-Length", StringComparison.OrdinalIgnoreCase))
if (int.TryParse(line.Substring(colon + 1).Trim(), out int n)) return n;
}
return -1;
}
}
}