| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081 |
- using System;
- using System.Collections.Generic;
- using System.Text;
- namespace ivf_tl_Operate.Debug
- {
- /// <summary>
- /// MJPEG multipart 流切帧状态机(纯逻辑)。喂入任意大小的字节块,吐出完整 JPEG 帧。
- /// 处理:一帧跨多个块、一个块含多帧、半个头跨块。只认 Content-Length 截帧(不靠扫 boundary 找帧尾,稳)。
- /// 流格式:--frame\r\nContent-Type: image/jpeg\r\nContent-Length: N\r\n\r\n[N 字节 JPEG]\r\n
- /// </summary>
- public sealed class MjpegFrameParser
- {
- private readonly List<byte> _buf = new List<byte>();
- private int _expectedLen = -1; // -1 = 还没解析到帧头;>=0 = 正在收帧体
- private int _bodyStart = -1;
- /// <summary>喂入一段字节,返回这次能切出的完整 JPEG 帧(可能 0~多帧)。</summary>
- public IEnumerable<byte[]> Feed(byte[] chunk, int count)
- {
- for (int i = 0; i < count; i++) _buf.Add(chunk[i]);
- var frames = new List<byte[]>();
- 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<byte> 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;
- }
- }
- }
|