From 1179d180361e843dac1c680cf5fe086054d4e4f1 Mon Sep 17 00:00:00 2001
From: smallchi <564952747@qq.com>
Date: Tue, 24 Sep 2019 18:36:49 +0800
Subject: [PATCH] =?UTF-8?q?1.=E5=A2=9E=E5=8A=A0=E6=8C=87=E6=95=B0=E5=93=A5?=
=?UTF-8?q?=E4=BC=A6=E5=B8=83=E7=BC=96=E7=A0=81=E5=8F=8A=E6=B5=8B=E8=AF=95?=
=?UTF-8?q?=202.=E5=A2=9E=E5=8A=A0H264=E8=A7=A3=E6=9E=90=E5=99=A8=203.?=
=?UTF-8?q?=E4=BF=AE=E6=94=B9ci=E4=B8=BAcore3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.travis.yml | 2 +-
src/JT1078.Flv.Test/H264/NALUHeaderTest.cs | 20 ++
.../MessagePack/ExpGolombReaderTest.cs | 30 ++
src/JT1078.Flv/H264/H264Demuxer.cs | 66 ++++
src/JT1078.Flv/H264/H264NALU.cs | 16 +
src/JT1078.Flv/H264/NALUHeader.cs | 25 ++
src/JT1078.Flv/JT1078.Flv.csproj | 4 +
src/JT1078.Flv/MessagePack/EXPGolombReader.cs | 312 ++++++++++++++++++
.../Metadata/AVCDecoderConfigurationRecord.cs | 65 ++++
src/JT1078.Protocol/JT1078Demuxer.cs | 46 +++
src/JT1078.Protocol/JT1078Package.cs | 5 +
11 files changed, 590 insertions(+), 1 deletion(-)
create mode 100644 src/JT1078.Flv.Test/H264/NALUHeaderTest.cs
create mode 100644 src/JT1078.Flv.Test/MessagePack/ExpGolombReaderTest.cs
create mode 100644 src/JT1078.Flv/H264/H264Demuxer.cs
create mode 100644 src/JT1078.Flv/H264/H264NALU.cs
create mode 100644 src/JT1078.Flv/H264/NALUHeader.cs
create mode 100644 src/JT1078.Flv/MessagePack/EXPGolombReader.cs
create mode 100644 src/JT1078.Flv/Metadata/AVCDecoderConfigurationRecord.cs
create mode 100644 src/JT1078.Protocol/JT1078Demuxer.cs
diff --git a/.travis.yml b/.travis.yml
index 2e12c33..1843cf3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
language: csharp
solution: JT1078.sln
-dotnet: 2.2.101
+dotnet: 3.0.100
os: linux
mono: none
dist: trusty2
diff --git a/src/JT1078.Flv.Test/H264/NALUHeaderTest.cs b/src/JT1078.Flv.Test/H264/NALUHeaderTest.cs
new file mode 100644
index 0000000..ada335c
--- /dev/null
+++ b/src/JT1078.Flv.Test/H264/NALUHeaderTest.cs
@@ -0,0 +1,20 @@
+using JT1078.Flv.H264;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xunit;
+
+namespace JT1078.Flv.Test.H264
+{
+ public class NALUHeaderTest
+ {
+ [Fact]
+ public void Test1()
+ {
+ NALUHeader header = new NALUHeader(0xc0);
+ Assert.Equal(1, header.ForbiddenZeroBit);
+ Assert.Equal(2, header.NalRefIdc);
+ Assert.Equal(0, header.NalUnitType);
+ }
+ }
+}
diff --git a/src/JT1078.Flv.Test/MessagePack/ExpGolombReaderTest.cs b/src/JT1078.Flv.Test/MessagePack/ExpGolombReaderTest.cs
new file mode 100644
index 0000000..0df1920
--- /dev/null
+++ b/src/JT1078.Flv.Test/MessagePack/ExpGolombReaderTest.cs
@@ -0,0 +1,30 @@
+using System.Buffers.Binary;
+using Xunit;
+using JT1078.Flv.MessagePack;
+
+namespace JT1078.Flv.Test.MessagePack
+{
+ public class ExpGolombReaderTest
+ {
+ [Fact]
+ public void Test1()
+ {
+ ExpGolombReader h264GolombReader = new ExpGolombReader(new byte[] { 103, 77, 0, 20, 149, 168, 88, 37, 144, 0 });
+ var result = h264GolombReader.ReadSPS();
+ Assert.Equal(77, result.profileIdc);
+ Assert.Equal(0u, result.profileCompat);
+ Assert.Equal(20, result.levelIdc);
+ Assert.Equal(352, result.width);
+ Assert.Equal(288, result.height);
+ //profileIdc 77
+ //profileCompat 0
+ //levelIdc 20
+ //picOrderCntType 2
+ //picWidthInMbsMinus1 21
+ //picHeightInMapUnitsMinus1 17
+ //frameMbsOnlyFlag 1
+ //width 352
+ //height 288
+ }
+ }
+}
diff --git a/src/JT1078.Flv/H264/H264Demuxer.cs b/src/JT1078.Flv/H264/H264Demuxer.cs
new file mode 100644
index 0000000..4cc5015
--- /dev/null
+++ b/src/JT1078.Flv/H264/H264Demuxer.cs
@@ -0,0 +1,66 @@
+using JT1078.Flv.Extensions;
+using JT1078.Flv.MessagePack;
+using JT1078.Protocol;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace JT1078.Flv.H264
+{
+ public class H264Demuxer
+ {
+ public const string codecstring = "avc1.";
+
+
+ ///
+ /// Expunge any "Emulation Prevention" bytes from a "Raw Byte Sequence Payload"
+ ///
+ /// 防止竞争插入0x03
+ ///
+ ///
+ ///
+ public byte[] DiscardEmulationPreventionBytes(ReadOnlySpan srcBuffer)
+ {
+ int length = srcBuffer.Length;
+ List EPBPositions = new List();
+ int i = 1;
+ // Find all `Emulation Prevention Bytes`
+ while (i < length - 2)
+ {
+ if (srcBuffer[i] == 0 && srcBuffer[i + 1] == 0 && srcBuffer[i + 2] == 0x03)
+ {
+ EPBPositions.Add(i + 2);
+ i += 2;
+ }
+ else
+ {
+ i++;
+ }
+ }
+ // If no Emulation Prevention Bytes were found just return the original
+ // array
+ if (EPBPositions.Count == 0)
+ {
+ return srcBuffer.ToArray();
+ }
+ // Create a new array to hold the NAL unit data
+ int newLength = length - EPBPositions.Count;
+ byte[] newBuffer = new byte[newLength];
+ var sourceIndex = 0;
+ for (i = 0; i < newLength; sourceIndex++, i++)
+ {
+ if (sourceIndex == EPBPositions[0])
+ {
+ // Skip this byte
+ sourceIndex++;
+ // Remove this position index
+ EPBPositions.RemoveAt(0);
+ }
+ newBuffer[i] = srcBuffer[sourceIndex];
+ }
+ return newBuffer;
+ }
+ }
+}
diff --git a/src/JT1078.Flv/H264/H264NALU.cs b/src/JT1078.Flv/H264/H264NALU.cs
new file mode 100644
index 0000000..cde2ca6
--- /dev/null
+++ b/src/JT1078.Flv/H264/H264NALU.cs
@@ -0,0 +1,16 @@
+using JT1078.Protocol;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Flv.H264
+{
+ public class H264NALU
+ {
+ public readonly static byte[] Start1 = new byte[3] { 0, 0, 1 };
+ public readonly static byte[] Start2 = new byte[4] { 0, 0, 0, 1 };
+ public byte[] StartCodePrefix { get; set; }
+ public NALUHeader NALUHeader { get; set; }
+ public JT1078Package JT1078Package { get; set; }
+ }
+}
diff --git a/src/JT1078.Flv/H264/NALUHeader.cs b/src/JT1078.Flv/H264/NALUHeader.cs
new file mode 100644
index 0000000..e694d9a
--- /dev/null
+++ b/src/JT1078.Flv/H264/NALUHeader.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Flv.H264
+{
+ public struct NALUHeader
+ {
+ public NALUHeader(byte value)
+ {
+ ForbiddenZeroBit = (value & 0x80) >> 7;
+ NalRefIdc = (value & 0x60) >> 5;
+ NalUnitType = value & 0x1f;
+ }
+ public NALUHeader(ReadOnlySpan value)
+ {
+ ForbiddenZeroBit = (value[0] & 0x80) >> 7;
+ NalRefIdc = (value[0] & 0x60) >> 5;
+ NalUnitType = value[0] & 0x1f;
+ }
+ public int ForbiddenZeroBit { get; set; }
+ public int NalRefIdc { get; set; }
+ public int NalUnitType { get; set; }
+ }
+}
diff --git a/src/JT1078.Flv/JT1078.Flv.csproj b/src/JT1078.Flv/JT1078.Flv.csproj
index ba75397..2601a25 100644
--- a/src/JT1078.Flv/JT1078.Flv.csproj
+++ b/src/JT1078.Flv/JT1078.Flv.csproj
@@ -8,5 +8,9 @@
+
+
+
+
diff --git a/src/JT1078.Flv/MessagePack/EXPGolombReader.cs b/src/JT1078.Flv/MessagePack/EXPGolombReader.cs
new file mode 100644
index 0000000..00c1a38
--- /dev/null
+++ b/src/JT1078.Flv/MessagePack/EXPGolombReader.cs
@@ -0,0 +1,312 @@
+using System;
+using System.Buffers.Binary;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Flv.MessagePack
+{
+ ///
+ /// Exp-Golomb指数哥伦布编码
+ ///
+ public ref struct ExpGolombReader
+ {
+ public ReadOnlySpan SrcBuffer { get; }
+ public int BytesAvailable { get; private set; }
+ public int Word { get; private set; }
+ public int BitsAvailable { get; private set; }
+ public ExpGolombReader(ReadOnlySpan srcBuffer)
+ {
+ SrcBuffer = srcBuffer;
+ BytesAvailable = srcBuffer.Length;
+ Word = 0;
+ BitsAvailable = 0;
+ }
+ public (byte profileIdc,byte levelIdc,uint profileCompat,int width, int height) ReadSPS()
+ {
+ int sarScale = 1;
+ uint frameCropLeftOffset=0;
+ uint frameCropRightOffset = 0;
+ uint frameCropTopOffset = 0;
+ uint frameCropBottomOffset = 0;
+ ReadByte();
+ //profile_idc
+ byte profileIdc = ReadByte();
+ //constraint_set[0-4]_flag, u(5)
+ uint profileCompat = ReadBits(5);
+ //reserved_zero_3bits
+ SkipBits(3);
+ //level_idc u(8)
+ byte levelIdc = ReadByte();
+ //seq_parameter_set_id
+ SkipUEG();
+ if (profileIdc == 100 ||
+ profileIdc == 110 ||
+ profileIdc == 122 ||
+ profileIdc == 244 ||
+ profileIdc == 44 ||
+ profileIdc == 83 ||
+ profileIdc == 86 ||
+ profileIdc == 118 ||
+ profileIdc == 128)
+ {
+ uint chromaFormatIdc = ReadUEG();
+ if (chromaFormatIdc == 3)
+ {
+ SkipBits(1); // separate_colour_plane_flag
+ }
+ SkipUEG(); // bit_depth_luma_minus8
+ SkipUEG(); // bit_depth_chroma_minus8
+ SkipBits(1); // qpprime_y_zero_transform_bypass_flag
+ if (ReadBoolean())
+ { // seq_scaling_matrix_present_flag
+ int scalingListCount = (chromaFormatIdc != 3) ? 8 : 12;
+ for (int i = 0; i < scalingListCount; i++)
+ {
+ if (ReadBoolean())
+ { // seq_scaling_list_present_flag[ i ]
+ if (i < 6)
+ {
+ SkipScalingList(16);
+ }
+ else
+ {
+ SkipScalingList(64);
+ }
+ }
+ }
+ }
+ }
+ // log2_max_frame_num_minus4
+ SkipUEG();
+ var picOrderCntType = ReadUEG();
+ if (picOrderCntType == 0)
+ {
+ ReadUEG(); //log2_max_pic_order_cnt_lsb_minus4
+ }
+ else if (picOrderCntType == 1)
+ {
+ SkipBits(1); // delta_pic_order_always_zero_flag
+ SkipEG(); // offset_for_non_ref_pic
+ SkipEG(); // offset_for_top_to_bottom_field
+ uint numRefFramesInPicOrderCntCycle = ReadUEG();
+ for (int i = 0; i < numRefFramesInPicOrderCntCycle; i++)
+ {
+ SkipEG(); // offset_for_ref_frame[ i ]
+ }
+ }
+ SkipUEG(); // max_num_ref_frames
+ SkipBits(1); // gaps_in_frame_num_value_allowed_flag
+ uint picWidthInMbsMinus1 = ReadUEG();
+ uint picHeightInMapUnitsMinus1 = ReadUEG();
+ uint frameMbsOnlyFlag = ReadBits(1);
+ if (frameMbsOnlyFlag == 0)
+ {
+ SkipBits(1); // mb_adaptive_frame_field_flag
+ }
+ this.SkipBits(1); // direct_8x8_inference_flag
+ if (ReadBoolean())
+ {
+ // frame_cropping_flag
+ frameCropLeftOffset = ReadUEG();
+ frameCropRightOffset = ReadUEG();
+ frameCropTopOffset = ReadUEG();
+ frameCropBottomOffset = ReadUEG();
+ }
+ if (ReadBoolean())
+ {
+ // vui_parameters_present_flag
+ if (ReadBoolean())
+ {
+ // aspect_ratio_info_present_flag
+ byte[] sarRatio=null;
+ byte aspectRatioIdc = ReadByte();
+ switch (aspectRatioIdc)
+ {
+ case 1: sarRatio =new byte[2] { 1, 1 }; break;
+ case 2: sarRatio =new byte[2] { 12, 11}; break;
+ case 3: sarRatio =new byte[2] { 10, 11}; break;
+ case 4: sarRatio =new byte[2] { 16, 11}; break;
+ case 5: sarRatio =new byte[2] { 40, 33}; break;
+ case 6: sarRatio =new byte[2] { 24, 11}; break;
+ case 7: sarRatio =new byte[2] { 20, 11}; break;
+ case 8: sarRatio =new byte[2] { 32, 11 }; break;
+ case 9: sarRatio = new byte[2] {80, 33 }; break;
+ case 10: sarRatio = new byte[2]{18, 11 }; break;
+ case 11: sarRatio = new byte[2]{15, 11 }; break;
+ case 12: sarRatio = new byte[2]{64, 33 }; break;
+ case 13: sarRatio = new byte[2]{160, 99 }; break;
+ case 14: sarRatio = new byte[2]{4, 3 }; break;
+ case 15: sarRatio = new byte[2]{3, 2 }; break;
+ case 16: sarRatio = new byte[2]{ 2, 1 }; break;
+ case 255:
+ {
+ sarRatio = new byte[2] { (byte)(ReadByte() << 8 | ReadByte()), (byte)(ReadByte() << 8 | ReadByte()) };
+ break;
+ }
+ }
+ if (sarRatio != null)
+ {
+ sarScale = sarRatio[0] / sarRatio[1];
+ }
+ }
+ }
+ int width= (int)((((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale);
+ int height = (int)(((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - ((frameMbsOnlyFlag == 1U ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset)));
+ return (profileIdc, levelIdc, profileCompat,width, height);
+ }
+ public void LoadWord()
+ {
+ var position = SrcBuffer.Length - BytesAvailable;
+ int tmpAvailableBytes = BytesAvailable - 4;
+ int availableBytes = Math.Min(4, BytesAvailable);
+ //if (availableBytes == 0)
+ //{
+ // throw new OverflowException("no bytes available");
+ //}
+ ReadOnlySpan workingBytes=ReadOnlySpan.Empty;
+ if (tmpAvailableBytes < 0)
+ {
+ var buffer = new byte[4];
+ Array.Copy(SrcBuffer.Slice(position, BytesAvailable).ToArray(), buffer, BytesAvailable);
+ workingBytes = buffer;
+ }
+ else
+ {
+ workingBytes = SrcBuffer.Slice(position, 4);
+ }
+ Word = BinaryPrimitives.ReadInt32BigEndian(workingBytes);
+ // track the amount of this.data that has been processed
+ BitsAvailable = availableBytes * 8;
+ BytesAvailable -= availableBytes;
+ }
+ public void SkipBits(int count)
+ {
+ if (BitsAvailable > count)
+ {
+ Word <<= count;
+ BitsAvailable -= count;
+ }
+ else
+ {
+ count -= BitsAvailable;
+ int skipBytes = count >> 3;
+ count -= (skipBytes >> 3);
+ LoadWord();
+ Word <<= count;
+ BitsAvailable -= count;
+ }
+ }
+ public uint ReadBits(int size)
+ {
+ var bits = Math.Min(BitsAvailable, size); // :uint
+ var valu = (uint)Word >> (32 - bits); // :uint
+ if (size > 32)
+ {
+ throw new OverflowException("Cannot read more than 32 bits at a time");
+ }
+ BitsAvailable -= bits;
+ if (BitsAvailable > 0)
+ {
+ Word <<= bits;
+ }
+ else if (BytesAvailable > 0)
+ {
+ LoadWord();
+ }
+ bits = size - bits;
+ if (bits > 0)
+ {
+ return ((valu << bits) | ReadBits(bits));
+ }
+ else
+ {
+ return valu;
+ }
+ }
+ public int SkipLZ()
+ {
+ int leadingZeroCount; // :uint
+ for (leadingZeroCount = 0; leadingZeroCount < this.BitsAvailable; ++leadingZeroCount)
+ {
+ if (0 != (Word & (0x80000000 >> leadingZeroCount)))
+ {
+ // the first bit of working word is 1
+ Word <<= leadingZeroCount;
+ BitsAvailable -= leadingZeroCount;
+ return leadingZeroCount;
+ }
+ }
+ // we exhausted word and still have not found a 1
+ LoadWord();
+ return (leadingZeroCount + SkipLZ());
+ }
+ public void SkipUEG()
+ {
+ SkipBits(1 + SkipLZ());
+ }
+ public void SkipEG()
+ {
+ SkipBits(1 + SkipLZ());
+ }
+ public uint ReadUEG()
+ {
+ var clz =SkipLZ();
+ return ReadBits(clz + 1) - 1;
+ }
+ public int ReadEG()
+ {
+ var valu = (int)ReadUEG(); // :int
+ if ((0x01 & valu)==1)
+ {
+ // the number is odd if the low order bit is set
+ return (1 + valu) >> 1; // add 1 to make it even, and divide by 2
+ }
+ else
+ {
+ return -1 * (valu >> 1); // divide by two then make it negative
+ }
+ }
+ public bool ReadBoolean()
+ {
+ return 1 == ReadBits(1);
+ }
+ public byte ReadByte()
+ {
+ return (byte)ReadBits(8);
+ }
+ public ushort ReadUShort()
+ {
+ return (ushort)ReadBits(16);
+ }
+ public uint ReadUInt()
+ {
+ return ReadBits(32);
+ }
+
+ ///
+ ///Advance the ExpGolomb decoder past a scaling list.The scaling
+ ///list is optionally transmitted as part of a sequence parameter
+ ///set and is not relevant to transmuxing.
+ ///@param count { number}
+ ///the number of entries in this scaling list
+ ///@see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
+ ///
+ ///
+ public void SkipScalingList(int count)
+ {
+ int lastScale = 8,
+ nextScale = 8,
+ j,
+ deltaScale;
+ for (j = 0; j < count; j++)
+ {
+ if (nextScale != 0)
+ {
+ deltaScale = ReadEG();
+ nextScale = (lastScale + deltaScale + 256) % 256;
+ }
+ lastScale = (nextScale == 0) ? lastScale : nextScale;
+ }
+ }
+ }
+}
diff --git a/src/JT1078.Flv/Metadata/AVCDecoderConfigurationRecord.cs b/src/JT1078.Flv/Metadata/AVCDecoderConfigurationRecord.cs
new file mode 100644
index 0000000..625d386
--- /dev/null
+++ b/src/JT1078.Flv/Metadata/AVCDecoderConfigurationRecord.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Flv.Metadata
+{
+
+
+ ///
+ ///
+ ///AVCDecoderConfigurationRecord 结构的定义:
+ ///aligned(8) class AVCDecoderConfigurationRecord
+ ///{
+ ///unsigned int (8) configurationVersion = 1;
+ ///unsigned int (8) AVCProfileIndication;
+ ///unsigned int (8) profile_compatibility;
+ ///unsigned int (8) AVCLevelIndication;
+ ///bit(6) reserved = ‘111111’b;
+ ///unsigned int (2) lengthSizeMinusOne;
+ ///bit(3) reserved = ‘111’b;
+ ///unsigned int (5) numOfSequenceParameterSets;
+ ///for (i=0; i
+ ///
+ public class AVCDecoderConfigurationRecord
+ {
+ public byte ConfigurationVersion { get; set; } = 1;
+ public byte AVCProfileIndication { get; set; }
+ public byte ProfileCompatibility { get; set; }
+ public byte AVCLevelIndication { get; set; }
+ public int LengthSizeMinusOne { get; set; }
+ public int NumOfSequenceParameterSets { get; set; }
+ public List SPS { get; set; }
+ public byte[] SPSBuffer { get; set; }
+ public byte NumOfPictureParameterSets { get; set; } = 1;
+ public List PPS { get; set; }
+ public byte[] PPSBuffer { get; set; }
+ #region Just for non-spec-conform encoders ref:org.mp4parser.boxes.iso14496.part15.AvcDecoderConfigurationRecord
+ public const int LengthSizeMinusOnePaddingBits = 63;
+ public const int NumberOfSequenceParameterSetsPaddingBits = 7;
+ public const int ChromaFormatPaddingBits = 31;
+ public const int BitDepthLumaMinus8PaddingBits = 31;
+ public const int BitDepthChromaMinus8PaddingBits = 31;
+ #endregion
+ public struct SPSInfo
+ {
+ public ushort SequenceParameterSetLength { get; set; }
+ public byte[] SequenceParameterSetNALUnit { get; set; }
+ }
+ public struct PPSInfo
+ {
+ public ushort PictureParameterSetLength { get; set; }
+ public byte[] PictureParameterSetNALUnit { get; set; }
+ }
+ }
+}
diff --git a/src/JT1078.Protocol/JT1078Demuxer.cs b/src/JT1078.Protocol/JT1078Demuxer.cs
new file mode 100644
index 0000000..6a6a20b
--- /dev/null
+++ b/src/JT1078.Protocol/JT1078Demuxer.cs
@@ -0,0 +1,46 @@
+using JT1078.Protocol.Enums;
+using System;
+using System.Linq;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Protocol
+{
+ public static class JT1078Demuxer
+ {
+ private readonly static ConcurrentDictionary JT1078PackageGroupDict = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
+ public static JT1078Package Demuxer(JT1078Package jT1078Package)
+ {
+ string cacheKey = jT1078Package.GetKey();
+ if (jT1078Package.Label3.SubpackageType == JT1078SubPackageType.分包处理时的第一个包)
+ {
+ JT1078PackageGroupDict.TryRemove(cacheKey, out _);
+ JT1078PackageGroupDict.TryAdd(cacheKey, jT1078Package);
+ return default;
+ }
+ else if (jT1078Package.Label3.SubpackageType == JT1078SubPackageType.分包处理时的中间包)
+ {
+ if (JT1078PackageGroupDict.TryGetValue(cacheKey, out var tmpPackage))
+ {
+ tmpPackage.Bodies.Concat(jT1078Package.Bodies).ToArray();
+ JT1078PackageGroupDict[cacheKey] = tmpPackage;
+ }
+ return default;
+ }
+ else if (jT1078Package.Label3.SubpackageType == JT1078SubPackageType.分包处理时的最后一个包)
+ {
+ if (JT1078PackageGroupDict.TryGetValue(cacheKey, out var tmpPackage))
+ {
+ tmpPackage.Bodies.Concat(jT1078Package.Bodies).ToArray();
+ return tmpPackage;
+ }
+ return default;
+ }
+ else
+ {
+ return jT1078Package;
+ }
+ }
+ }
+}
diff --git a/src/JT1078.Protocol/JT1078Package.cs b/src/JT1078.Protocol/JT1078Package.cs
index b2be396..92a7692 100644
--- a/src/JT1078.Protocol/JT1078Package.cs
+++ b/src/JT1078.Protocol/JT1078Package.cs
@@ -86,5 +86,10 @@ namespace JT1078.Protocol
/// 数据体
///
public byte[] Bodies{ get; set; }
+
+ public string GetKey()
+ {
+ return $"{SIM}_{LogicChannelNumber.ToString()}";
+ }
}
}