MjpegFrameParser.cs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. namespace ivf_tl_Operate.Debug
  5. {
  6. /// <summary>
  7. /// MJPEG multipart 流切帧状态机(纯逻辑)。喂入任意大小的字节块,吐出完整 JPEG 帧。
  8. /// 处理:一帧跨多个块、一个块含多帧、半个头跨块。只认 Content-Length 截帧(不靠扫 boundary 找帧尾,稳)。
  9. /// 流格式:--frame\r\nContent-Type: image/jpeg\r\nContent-Length: N\r\n\r\n[N 字节 JPEG]\r\n
  10. /// </summary>
  11. public sealed class MjpegFrameParser
  12. {
  13. private readonly List<byte> _buf = new List<byte>();
  14. private int _expectedLen = -1; // -1 = 还没解析到帧头;>=0 = 正在收帧体
  15. private int _bodyStart = -1;
  16. /// <summary>喂入一段字节,返回这次能切出的完整 JPEG 帧(可能 0~多帧)。</summary>
  17. public IEnumerable<byte[]> Feed(byte[] chunk, int count)
  18. {
  19. for (int i = 0; i < count; i++) _buf.Add(chunk[i]);
  20. var frames = new List<byte[]>();
  21. while (true)
  22. {
  23. if (_expectedLen < 0)
  24. {
  25. // 找头部结束标志 \r\n\r\n
  26. int sep = IndexOfDoubleCrlf(_buf);
  27. if (sep < 0) break; // 头还没收全,等下一块
  28. string header = Encoding.ASCII.GetString(_buf.ToArray(), 0, sep);
  29. int len = ParseContentLength(header);
  30. if (len < 0)
  31. {
  32. // 头里没 Content-Length(异常/坏帧)→ 丢弃到分隔符后,继续
  33. _buf.RemoveRange(0, sep + 4);
  34. continue;
  35. }
  36. _expectedLen = len;
  37. _bodyStart = sep + 4; // \r\n\r\n 之后是帧体
  38. }
  39. // 收帧体:需要 _bodyStart + _expectedLen 字节
  40. if (_buf.Count < _bodyStart + _expectedLen) break; // 帧体没收全,等下一块
  41. var jpeg = new byte[_expectedLen];
  42. _buf.CopyTo(_bodyStart, jpeg, 0, _expectedLen);
  43. frames.Add(jpeg);
  44. // 移除已消费的帧(头 + 体 + 可能的尾 \r\n)。下一帧从 --frame 开始,统一靠下轮找 \r\n\r\n。
  45. int consumed = _bodyStart + _expectedLen;
  46. // 跳过帧体后的 \r\n(若存在)
  47. if (_buf.Count >= consumed + 2 && _buf[consumed] == (byte)'\r' && _buf[consumed + 1] == (byte)'\n')
  48. consumed += 2;
  49. _buf.RemoveRange(0, consumed);
  50. _expectedLen = -1; _bodyStart = -1;
  51. }
  52. return frames;
  53. }
  54. private static int IndexOfDoubleCrlf(List<byte> buf)
  55. {
  56. for (int i = 0; i + 3 < buf.Count; i++)
  57. if (buf[i] == (byte)'\r' && buf[i + 1] == (byte)'\n' && buf[i + 2] == (byte)'\r' && buf[i + 3] == (byte)'\n')
  58. return i;
  59. return -1;
  60. }
  61. private static int ParseContentLength(string header)
  62. {
  63. foreach (var line in header.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries))
  64. {
  65. int colon = line.IndexOf(':');
  66. if (colon < 0) continue;
  67. if (line.Substring(0, colon).Trim().Equals("Content-Length", StringComparison.OrdinalIgnoreCase))
  68. if (int.TryParse(line.Substring(colon + 1).Trim(), out int n)) return n;
  69. }
  70. return -1;
  71. }
  72. }
  73. }