diff --git a/doc/video/demo.m3u8 b/doc/video/demo.m3u8
new file mode 100644
index 0000000..aa5fea3
--- /dev/null
+++ b/doc/video/demo.m3u8
@@ -0,0 +1,7 @@
+#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-TARGETDURATION:7
+#EXT-X-MEDIA-SEQUENCE:0
+#EXTINF:7.200667,
+demo0.ts
+#EXT-X-ENDLIST
diff --git a/doc/video/demo0.ts b/doc/video/demo0.ts
new file mode 100644
index 0000000..e3cc2ab
Binary files /dev/null and b/doc/video/demo0.ts differ
diff --git a/src/JT1078.Hls.Test/JT1078.Hls.Test.csproj b/src/JT1078.Hls.Test/JT1078.Hls.Test.csproj
new file mode 100644
index 0000000..ed587fa
--- /dev/null
+++ b/src/JT1078.Hls.Test/JT1078.Hls.Test.csproj
@@ -0,0 +1,20 @@
+
+
+
+ netcoreapp3.1
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/JT1078.Hls.Test/UnitTest1.cs b/src/JT1078.Hls.Test/UnitTest1.cs
new file mode 100644
index 0000000..7f9854e
--- /dev/null
+++ b/src/JT1078.Hls.Test/UnitTest1.cs
@@ -0,0 +1,55 @@
+using System;
+using Xunit;
+
+namespace JT1078.Hls.Test
+{
+ public class UnitTest1
+ {
+ [Fact]
+ public void Test1()
+ {
+
+ }
+ //---------PMT
+ //47 50 00 10 00 02 B0 1D 00 01 C1 00 00 E1 00 F0 00 1B E1 00 F0 00 0F E1 01 F0 06 0A 04 75 6E 64 00 08 7D E8 77
+ //47
+ //50 00
+ //10
+ //00
+ //02
+ //B0 1D
+ //00 01
+ //C1
+ //00
+ //00
+ //E1 00
+ //F0 00
+ //1B
+ //E1 00
+ // F0 00
+ //0F
+ //E1 01
+ // F0 06
+ // 0A
+ // 04 75
+ // 6E 64
+ // 00
+ //08 7D E8 77
+
+ //----------PAT
+ //47 40 00 10 00 00 B0 0D 00 01 C1 00 00 00 01 F0 00 2A B1 04 B2
+ //47
+ //40 00
+ //10
+ //00
+ //00
+ //B0 0D
+ //00 01
+ //C1
+ //00
+ //00
+ //00 01
+ //F0 00
+ //2A B1 04 B2
+ }
+}
diff --git a/src/JT1078.Hls/Buffers/TSBufferWriter.cs b/src/JT1078.Hls/Buffers/TSBufferWriter.cs
new file mode 100644
index 0000000..95d88d7
--- /dev/null
+++ b/src/JT1078.Hls/Buffers/TSBufferWriter.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace JT1078.Hls
+{
+ ///
+ ///
+ ///
+ ref partial struct TSBufferWriter
+ {
+ private Span _buffer;
+ public TSBufferWriter(Span buffer)
+ {
+ _buffer = buffer;
+ WrittenCount = 0;
+ }
+ public Span Free => _buffer.Slice(WrittenCount);
+ public Span Written => _buffer.Slice(0, WrittenCount);
+ public int WrittenCount { get; private set; }
+ public void Advance(int count)
+ {
+ WrittenCount += count;
+ }
+ }
+}
diff --git a/src/JT1078.Hls/ES_Package.cs b/src/JT1078.Hls/ES_Package.cs
new file mode 100644
index 0000000..6059e68
--- /dev/null
+++ b/src/JT1078.Hls/ES_Package.cs
@@ -0,0 +1,28 @@
+using JT1078.Hls.Formatters;
+using JT1078.Hls.MessagePack;
+using JT1078.Protocol.H264;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Hls
+{
+ public class ES_Package: ITSMessagePackFormatter
+ {
+ public static byte[] NALU0X09 = new byte[] { 0x00, 0x00, 0x00, 0x01, 0x09, 0xFF };
+ public byte[] NALU0x09 { get; set; } = NALU0X09;
+ public List NALUs { get; set; }
+ public void ToBuffer(ref TSMessagePackWriter writer)
+ {
+ writer.WriteArray(NALU0x09);
+ if(NALUs!=null)
+ {
+ foreach(var nalu in NALUs)
+ {
+ writer.WriteArray(nalu.StartCodePrefix);
+ writer.WriteArray(nalu.RawData);
+ }
+ }
+ }
+ }
+}
diff --git a/src/JT1078.Hls/Enums/AdaptationFieldControl.cs b/src/JT1078.Hls/Enums/AdaptationFieldControl.cs
new file mode 100644
index 0000000..b549a57
--- /dev/null
+++ b/src/JT1078.Hls/Enums/AdaptationFieldControl.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Hls.Enums
+{
+ public enum AdaptationFieldControl
+ {
+ 保留= 0000_0000,
+ 无自适应域_仅含有效负载 = 0001_0000,
+ 仅含自适应域_无有效负载 = 0010_0000,
+ 同时带有自适应域和有效负载 = 0011_0000,
+ }
+}
diff --git a/src/JT1078.Hls/Enums/PCRInclude.cs b/src/JT1078.Hls/Enums/PCRInclude.cs
new file mode 100644
index 0000000..dcc21a2
--- /dev/null
+++ b/src/JT1078.Hls/Enums/PCRInclude.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Hls.Enums
+{
+ ///
+ /// 取0x50表示包含PCR或0x40表示不包含PCR
+ ///
+ public enum PCRInclude:byte
+ {
+ 包含= 0x50,
+ 不包含= 0x40
+ }
+}
diff --git a/src/JT1078.Hls/Enums/PTS_DTS_Flags.cs b/src/JT1078.Hls/Enums/PTS_DTS_Flags.cs
new file mode 100644
index 0000000..63eb618
--- /dev/null
+++ b/src/JT1078.Hls/Enums/PTS_DTS_Flags.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Hls.Enums
+{
+ ///
+ ///
+ ///
+ public enum PTS_DTS_Flags:byte
+ {
+ all = 0xc0,
+ pts = 0x80,
+ dts = 0x40
+ }
+}
diff --git a/src/JT1078.Hls/Enums/StreamType.cs b/src/JT1078.Hls/Enums/StreamType.cs
new file mode 100644
index 0000000..3348e26
--- /dev/null
+++ b/src/JT1078.Hls/Enums/StreamType.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Hls.Enums
+{
+ public enum StreamType:byte
+ {
+ h264 = 0x1B,
+ aac = 0x0f,
+ mp3 = 0x03
+ }
+}
diff --git a/src/JT1078.Hls/Interfaces/ITSMessagePackFormatter.cs b/src/JT1078.Hls/Interfaces/ITSMessagePackFormatter.cs
new file mode 100644
index 0000000..2ad3854
--- /dev/null
+++ b/src/JT1078.Hls/Interfaces/ITSMessagePackFormatter.cs
@@ -0,0 +1,10 @@
+using JT1078.Hls.MessagePack;
+using System;
+
+namespace JT1078.Hls.Formatters
+{
+ public interface ITSMessagePackFormatter
+ {
+ void ToBuffer(ref TSMessagePackWriter writer);
+ }
+}
diff --git a/src/JT1078.Hls/MessagePack/TSMessagePackWriter.cs b/src/JT1078.Hls/MessagePack/TSMessagePackWriter.cs
new file mode 100644
index 0000000..a4c3b54
--- /dev/null
+++ b/src/JT1078.Hls/MessagePack/TSMessagePackWriter.cs
@@ -0,0 +1,163 @@
+using System;
+using System.Buffers.Binary;
+
+namespace JT1078.Hls.MessagePack
+{
+ public ref partial struct TSMessagePackWriter
+ {
+ private TSBufferWriter writer;
+ public TSMessagePackWriter(Span buffer)
+ {
+ this.writer = new TSBufferWriter(buffer);
+ }
+ public byte[] FlushAndGetArray()
+ {
+ return writer.Written.ToArray();
+ }
+ public void WriteByte(byte value)
+ {
+ var span = writer.Free;
+ span[0] = value;
+ writer.Advance(1);
+ }
+ public void WriteUInt16(ushort value)
+ {
+ BinaryPrimitives.WriteUInt16BigEndian(writer.Free, value);
+ writer.Advance(2);
+ }
+ public void WriteInt32(int value)
+ {
+ BinaryPrimitives.WriteInt32BigEndian(writer.Free, value);
+ writer.Advance(4);
+ }
+ public void WriteUInt64(ulong value)
+ {
+ BinaryPrimitives.WriteUInt64BigEndian(writer.Free, value);
+ writer.Advance(8);
+ }
+ public void WriteUInt5(ulong value)
+ {
+ writer.Free[0] = (byte)(value >> 32);
+ writer.Free[1] = (byte)(value >> 24);
+ writer.Free[2] = (byte)(value >> 16);
+ writer.Free[3] = (byte)(value >> 8);
+ writer.Free[4] = (byte)(value);
+ writer.Advance(5);
+ }
+ public void WriteUInt6(ulong value)
+ {
+ writer.Free[0] = (byte)(value >> 40);
+ writer.Free[1] = (byte)(value >> 32);
+ writer.Free[2] = (byte)(value >> 24);
+ writer.Free[3] = (byte)(value >> 16);
+ writer.Free[4] = (byte)(value >> 8);
+ writer.Free[5] = (byte)(value);
+ writer.Advance(6);
+ }
+ public void WriteInt5(long value)
+ {
+ writer.Free[0] = (byte)(value >> 32);
+ writer.Free[1] = (byte)(value >> 24);
+ writer.Free[2] = (byte)(value >> 16);
+ writer.Free[3] = (byte)(value >> 8);
+ writer.Free[4] = (byte)(value);
+ writer.Advance(5);
+ }
+ public void WriteInt6(long value)
+ {
+ writer.Free[0] = (byte)(value >> 40);
+ writer.Free[1] = (byte)(value >> 32);
+ writer.Free[2] = (byte)(value >> 24);
+ writer.Free[3] = (byte)(value >> 16);
+ writer.Free[4] = (byte)(value >> 8);
+ writer.Free[5] = (byte)(value);
+ writer.Advance(6);
+ }
+ public void WriteUInt3(uint value)
+ {
+ writer.Free[0] = (byte)(value >> 16);
+ writer.Free[1] = (byte)(value >> 8);
+ writer.Free[2] = (byte)(value);
+ writer.Advance(3);
+ }
+ public void WriteInt3(int value)
+ {
+ writer.Free[0] = (byte)(value >> 16);
+ writer.Free[1] = (byte)(value >> 8);
+ writer.Free[2] = (byte)(value);
+ writer.Advance(3);
+ }
+ public void WriteUInt32(uint value)
+ {
+ BinaryPrimitives.WriteUInt32BigEndian(writer.Free, value);
+ writer.Advance(4);
+ }
+ public void WriteArray(ReadOnlySpan src)
+ {
+ src.CopyTo(writer.Free);
+ writer.Advance(src.Length);
+ }
+ public void Skip(int count, out int position)
+ {
+ position = writer.WrittenCount;
+ byte[] tmp = new byte[count];
+ tmp.CopyTo(writer.Free);
+ writer.Advance(count);
+ }
+ public void WriteCRC32()
+ {
+ if (writer.WrittenCount < 1)
+ {
+ throw new ArgumentOutOfRangeException($"Written{1}");
+ }
+ //从第1位开始
+ var crcSpan = writer.Written.Slice(1);
+ uint crc = 0xFFFFFFFF;
+ for (int i = 0; i < crcSpan.Length; i++)
+ {
+ crc = (crc << 8) ^ Util.crcTable[(crc >> 24) ^ crcSpan[i]];
+ }
+ WriteUInt32(crc);
+ }
+ public void WriteCRC32(int start)
+ {
+ if (writer.WrittenCount < start)
+ {
+ throw new ArgumentOutOfRangeException($"Written{1}");
+ }
+ var crcSpan = writer.Written.Slice(start);
+ uint crc = 0xFFFFFFFF;
+ for (int i = 0; i < crcSpan.Length; i++)
+ {
+ crc = (crc << 8) ^ Util.crcTable[(crc >> 24) ^ crcSpan[i]];
+ }
+ WriteUInt32(crc);
+ }
+ public void WriteCRC32(int start, int end)
+ {
+ if (start > end)
+ {
+ throw new ArgumentOutOfRangeException($"start>end:{start}>{end}");
+ }
+ var crcSpan = writer.Written.Slice(start, end);
+ uint crc = 0xFFFFFFFF;
+ for (int i = 0; i < crcSpan.Length; i++)
+ {
+ crc = ((crc << 8) ^ Util.crcTable[(crc >> 8) ^ crcSpan[i]]);
+ }
+ WriteUInt32(crc);
+ }
+ public void WriteUInt16Return(ushort value, int position)
+ {
+ BinaryPrimitives.WriteUInt16BigEndian(writer.Written.Slice(position, 2), value);
+ }
+ public void WriteByteReturn(byte value, int position)
+ {
+ writer.Written[position] = value;
+ }
+ public int GetCurrentPosition()
+ {
+ return writer.WrittenCount;
+ }
+ }
+}
diff --git a/src/JT1078.Hls/PES_Package.cs b/src/JT1078.Hls/PES_Package.cs
new file mode 100644
index 0000000..a0f8468
--- /dev/null
+++ b/src/JT1078.Hls/PES_Package.cs
@@ -0,0 +1,81 @@
+using JT1078.Hls.Enums;
+using JT1078.Hls.Formatters;
+using JT1078.Hls.MessagePack;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Hls
+{
+ public class PES_Package: ITSMessagePackFormatter
+ {
+ public static byte[] PESSTARCODE = new byte[] { 0x00, 0x00, 0x01 };
+ ///
+ /// 开始码,固定为0x000001
+ ///
+ public byte[] PESStartCode { get; set; } = PESSTARCODE;
+ ///
+ /// 音频取值(0xc0-0xdf),通常为0xc0
+ /// 视频取值(0xe0-0xef),通常为0xe0
+ ///
+ public byte StreamId { get; set; }
+ ///
+ /// 后面pes数据的长度,0表示长度不限制,只有视频数据长度会超过0xffff
+ ///
+ public ushort PESPacketLength { get; set; }
+ ///
+ /// 通常取值0x80,表示数据不加密、无优先级、备份的数据
+ /// ISOIEC13818-1 120页 Table E-1 -- PES packet header example
+ ///
+ internal byte Flag1 { get; set; } = 0x80;
+ ///
+ /// 取值0x80表示只含有pts,取值0xc0表示含有pts和dts
+ /// ISOIEC13818-1 120页 Table E-1 -- PES packet header example
+ ///
+ public PTS_DTS_Flags PTS_DTS_Flag { get; set; }
+ ///
+ /// 根据PTS_DTS_Flag来判断后续长度
+ /// 后面数据的长度,取值5或10
+ ///
+ internal byte PESDataLength { get; set; }
+ ///
+ /// 5B
+ /// 33bit值
+ ///
+ public long PTS { get; set; }
+ ///
+ /// 5B
+ /// 33bit值
+ ///
+ public long DTS { get; set; }
+ ///
+ /// 音视频数据
+ ///
+ public ES_Package Payload { get; set; }
+ public void ToBuffer(ref TSMessagePackWriter writer)
+ {
+ writer.WriteArray(PESStartCode);
+ writer.WriteByte(StreamId);
+ writer.WriteUInt16(PESPacketLength);
+ writer.WriteByte(Flag1);
+ writer.WriteByte((byte)PTS_DTS_Flag);
+ if(PTS_DTS_Flag== PTS_DTS_Flags.all)
+ {
+ writer.WriteByte(10);
+ writer.WriteInt5(PTS);
+ writer.WriteInt5(DTS);
+ }
+ else if(PTS_DTS_Flag == PTS_DTS_Flags.pts)
+ {
+ writer.WriteByte(5);
+ writer.WriteInt5(PTS);
+ }
+ else if (PTS_DTS_Flag == PTS_DTS_Flags.dts)
+ {
+ writer.WriteByte(5);
+ writer.WriteInt5(DTS);
+ }
+ Payload.ToBuffer(ref writer);
+ }
+ }
+}
diff --git a/src/JT1078.Hls/TSArrayPool.cs b/src/JT1078.Hls/TSArrayPool.cs
new file mode 100644
index 0000000..2c59df4
--- /dev/null
+++ b/src/JT1078.Hls/TSArrayPool.cs
@@ -0,0 +1,24 @@
+using System.Buffers;
+
+namespace JT1078.Hls
+{
+ internal static class TSArrayPool
+ {
+ private readonly static ArrayPool ArrayPool;
+
+ static TSArrayPool()
+ {
+ ArrayPool = ArrayPool.Create();
+ }
+
+ public static byte[] Rent(int minimumLength)
+ {
+ return ArrayPool.Rent(minimumLength);
+ }
+
+ public static void Return(byte[] array, bool clearArray = false)
+ {
+ ArrayPool.Return(array, clearArray);
+ }
+ }
+}
diff --git a/src/JT1078.Hls/TSEncoder.cs b/src/JT1078.Hls/TSEncoder.cs
new file mode 100644
index 0000000..f01b016
--- /dev/null
+++ b/src/JT1078.Hls/TSEncoder.cs
@@ -0,0 +1,58 @@
+using JT1078.Protocol.Enums;
+using JT1078.Protocol.H264;
+using JT1078.Protocol.MessagePack;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using JT1078.Protocol;
+using JT1078.Hls.MessagePack;
+
+[assembly: InternalsVisibleTo("JT1078.Hls.Test")]
+
+namespace JT1078.Hls
+{
+ public class TSEncoder
+ {
+ readonly H264Decoder h264Decoder = new H264Decoder();
+
+ public byte[] Create(JT1078Package package, int minBufferSize = 65535)
+ {
+ byte[] buffer = TSArrayPool.Rent(minBufferSize);
+ try
+ {
+ TS_Package tS_Package = new TS_Package();
+ tS_Package.Header = new TS_Header();
+ tS_Package.Header.PID = (ushort)package.GetKey().GetHashCode();
+ tS_Package.Header.ContinuityCounter = (byte)package.SN;
+ tS_Package.Header.PayloadUnitStartIndicator = 1;
+ tS_Package.Header.Adaptation = new TS_AdaptationInfo();
+ tS_Package.Payload = new PES_Package();
+
+ TSMessagePackWriter messagePackReader = new TSMessagePackWriter(buffer);
+ var nalus = h264Decoder.ParseNALU(package);
+ if (nalus != null && nalus.Count > 0)
+ {
+ var sei = nalus.FirstOrDefault(x => x.NALUHeader.NalUnitType == 6);
+ var sps = nalus.FirstOrDefault(x => x.NALUHeader.NalUnitType == 7);
+ var pps = nalus.FirstOrDefault(x => x.NALUHeader.NalUnitType == 8);
+ nalus.Remove(sps);
+ nalus.Remove(pps);
+ nalus.Remove(sei);
+
+ foreach (var naln in nalus)
+ {
+
+ }
+ }
+
+
+ return messagePackReader.FlushAndGetArray();
+ }
+ finally
+ {
+ TSArrayPool.Return(buffer);
+ }
+ }
+ }
+}
diff --git a/src/JT1078.Hls/TS_AdaptationInfo.cs b/src/JT1078.Hls/TS_AdaptationInfo.cs
new file mode 100644
index 0000000..b80df07
--- /dev/null
+++ b/src/JT1078.Hls/TS_AdaptationInfo.cs
@@ -0,0 +1,32 @@
+using JT1078.Hls.Enums;
+using JT1078.Hls.Formatters;
+using JT1078.Hls.MessagePack;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Hls
+{
+ public class TS_AdaptationInfo : ITSMessagePackFormatter
+ {
+ ///
+ /// 取0x50表示包含PCR或0x40表示不包含PCR
+ /// 1B
+ ///
+ public PCRInclude PCRIncluded { get; set; }
+ ///
+ /// Program Clock Reference,节目时钟参考,用于恢复出与编码端一致的系统时序时钟STC(System Time Clock)
+ /// 5B
+ ///
+ public long PCR { get; set; }
+
+ public void ToBuffer(ref TSMessagePackWriter writer)
+ {
+ writer.WriteByte((byte)PCRIncluded);
+ if (PCRIncluded== PCRInclude.包含)
+ {
+ writer.WriteInt5(PCR);
+ }
+ }
+ }
+}
diff --git a/src/JT1078.Hls/TS_Header.cs b/src/JT1078.Hls/TS_Header.cs
new file mode 100644
index 0000000..c2b015c
--- /dev/null
+++ b/src/JT1078.Hls/TS_Header.cs
@@ -0,0 +1,79 @@
+using JT1078.Hls.Enums;
+using JT1078.Hls.Formatters;
+using JT1078.Hls.MessagePack;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Hls
+{
+ public class TS_Header : ITSMessagePackFormatter
+ {
+ ///
+ /// 同步字节,固定为0x47
+ ///
+ internal byte SyncByte { get; set; } = 0x47;
+ ///
+ /// 传输错误指示符,表明在ts头的adapt域后由一个无用字节,通常都为0,这个字节算在adapt域长度内
+ /// 1bit
+ ///
+ internal byte TransportErrorIndicator { get; set; } = 0;
+ ///
+ /// 负载单元起始标示符,一个完整的数据包开始时标记为1
+ /// 1bit
+ ///
+ public byte PayloadUnitStartIndicator { get; set; } = 1;
+ ///
+ /// 传输优先级,0为低优先级,1为高优先级,通常取0
+ /// 1bit
+ ///
+ internal byte TransportPriority { get; set; } = 0;
+ ///
+ /// pid值
+ /// 13bit
+ ///
+ public ushort PID { get; set; }
+ ///
+ /// 传输优先级,0为低优先级,1为高优先级,通常取0
+ /// 2bit
+ ///
+ internal byte TransportScramblingControl { get; set; } = 0;
+ ///
+ /// 是否包含自适应区,‘00’保留;‘01’为无自适应域,仅含有效负载;‘10’为仅含自适应域,无有效负载;‘11’为同时带有自适应域和有效负载。
+ /// 2bit
+ ///
+ public AdaptationFieldControl AdaptationFieldControl { get; set; }
+ ///
+ /// 递增计数器,从0-f,起始值不一定取0,但必须是连续的
+ /// 4bit
+ ///
+ public byte ContinuityCounter { get; set; } = 0;
+ ///
+ /// 自适应域长度,后面的字节数
+ ///
+ public byte AdaptationLength { get; set; }
+ ///
+ /// 附加字段
+ ///
+ public TS_AdaptationInfo Adaptation { get; set; }
+
+ public void ToBuffer(ref TSMessagePackWriter writer)
+ {
+ writer.WriteByte(SyncByte);
+ //TransportErrorIndicator PayloadUnitStartIndicator TransportPriority PID
+ //0 1 0 0000 0000 0000 0
+ writer.WriteUInt16((ushort)(0100_0000_0000_0000 | PID));
+ writer.WriteByte((byte)((int)AdaptationFieldControl | ContinuityCounter));
+ if (Adaptation != null)
+ {
+ writer.Skip(1, out int AdaptationLengthPosition);
+ Adaptation.ToBuffer(ref writer);
+ writer.WriteByteReturn((byte)(writer.GetCurrentPosition() - AdaptationLengthPosition - 1), AdaptationLengthPosition);
+ }
+ else
+ {
+ writer.WriteByte(0);
+ }
+ }
+ }
+}
diff --git a/src/JT1078.Hls/TS_PAT_Package.cs b/src/JT1078.Hls/TS_PAT_Package.cs
new file mode 100644
index 0000000..5844524
--- /dev/null
+++ b/src/JT1078.Hls/TS_PAT_Package.cs
@@ -0,0 +1,106 @@
+using JT1078.Hls.Formatters;
+using JT1078.Hls.MessagePack;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Hls
+{
+ ///
+ /// 格式节目关联表
+ ///
+ public class TS_PAT_Package : ITSMessagePackFormatter
+ {
+ ///
+ /// PAT表固定为0x00
+ /// 8bit
+ ///
+ public byte TableId { get; set; } = 0x00;
+ ///
+ /// 固定为二进制1
+ /// 1bit
+ ///
+ internal byte SectionSyntaxIndicator { get; set; } = 0x01;
+ ///
+ /// 固定为二进制0
+ /// 1bit
+ ///
+ internal byte Zero { get; set; } = 0x00;
+ ///
+ /// 固定为二进制3
+ /// 2bit
+ ///
+ internal byte Reserved1 { get; set; } = 0x03;
+ ///
+ /// 后面数据的长度
+ /// 12bit
+ ///
+ public ushort SectionLength { get; set; }
+ ///
+ /// 传输流ID
+ /// 16bit
+ ///
+ internal ushort TransportStreamId { get; set; } = 0x0001;
+ ///
+ /// 固定为二进制3
+ /// 2bit
+ ///
+ internal byte Reserved2 { get; set; } = 0x03;
+ ///
+ /// 版本号,固定为二进制00000,如果PAT有变化则版本号加1
+ /// 5bit
+ ///
+ public byte VersionNumber { get; set; } = 0x00;
+ ///
+ /// 固定为二进制1,表示这个PAT表可以用,如果为0则要等待下一个PAT表
+ /// 1bit
+ ///
+ public byte CurrentNextIndicator { get; set; } = 1;
+ ///
+ /// 固定为0x00
+ /// bit8
+ ///
+ internal byte SectionNumber { get; set; } = 0x00;
+ ///
+ /// 固定为0x00
+ /// bit8
+ ///
+ internal byte LastSectionNumber { get; set; } = 0x00;
+
+ public List Programs { get; set; }
+
+ ///
+ /// 前面数据的CRC32校验码
+ ///
+ public uint CRC32 { get; set; }
+
+ public void ToBuffer(ref TSMessagePackWriter writer)
+ {
+ writer.WriteByte(TableId);
+ //SectionSyntaxIndicator Zero Reserved1 SectionLength
+ //1 0 11 0000 0000 0000
+ //(ushort)(1011_0000_0000_0000 | SectionLength)
+ // todo:
+ //writer.WriteUInt16((ushort)(1011_0000_0000_0000 | SectionLength));
+ writer.Skip(2, out int SectionLengthPosition);
+ writer.WriteUInt16(TransportStreamId);
+ //Reserved2 VersionNumber CurrentNextIndicator
+ //11 00000 1
+ var a = Reserved2 & 0xC0;
+ var b = VersionNumber & 0x3E;
+ var c = (byte)(a | b | CurrentNextIndicator);
+ writer.WriteByte(c);
+ writer.WriteByte(SectionNumber);
+ writer.WriteByte(LastSectionNumber);
+ if (Programs != null)
+ {
+ foreach (var program in Programs)
+ {
+ program.ToBuffer(ref writer);
+ }
+ }
+ writer.WriteUInt16Return((ushort)(1011_0000_0000_0000 | (ushort)(writer.GetCurrentPosition() - SectionLengthPosition - 2)), SectionLengthPosition);
+ writer.WriteCRC32(SectionLengthPosition);
+ }
+ }
+}
diff --git a/src/JT1078.Hls/TS_PAT_Program.cs b/src/JT1078.Hls/TS_PAT_Program.cs
new file mode 100644
index 0000000..a464fe8
--- /dev/null
+++ b/src/JT1078.Hls/TS_PAT_Program.cs
@@ -0,0 +1,34 @@
+using JT1078.Hls.Formatters;
+using JT1078.Hls.MessagePack;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Hls
+{
+ public class TS_PAT_Program : ITSMessagePackFormatter
+ {
+ ///
+ /// 节目号为0x0000时表示这是NIT,节目号为0x0001时,表示这是PMT
+ /// 16bit
+ ///
+ public ushort ProgramNumber { get; set; }
+ ///
+ /// 固定为二进制111(7)
+ /// 1110_0000_0000_0000
+ /// 3bit
+ ///
+ internal byte Reserved1 { get; set; } = 0x07;
+ ///
+ /// 节目号对应内容的PID值
+ /// 13bit
+ ///
+ public ushort PID { get; set; }
+
+ public void ToBuffer(ref TSMessagePackWriter writer)
+ {
+ writer.WriteUInt16(ProgramNumber);
+ writer.WriteUInt16((ushort)(1110_0000_0000_0000 | PID));
+ }
+ }
+}
diff --git a/src/JT1078.Hls/TS_PMT_Component.cs b/src/JT1078.Hls/TS_PMT_Component.cs
new file mode 100644
index 0000000..63af051
--- /dev/null
+++ b/src/JT1078.Hls/TS_PMT_Component.cs
@@ -0,0 +1,47 @@
+using JT1078.Hls.Enums;
+using JT1078.Hls.Formatters;
+using JT1078.Hls.MessagePack;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Hls
+{
+ public class TS_PMT_Component: ITSMessagePackFormatter
+ {
+ ///
+ /// 流类型,标志是Video还是Audio还是其他数据,h.264编码对应0x1b,aac编码对应0x0f,mp3编码对应0x03
+ /// 8bit
+ ///
+ public StreamType StreamType { get; set; }
+ ///
+ /// 固定为二进制111(7)
+ /// 0111_0000_0000_0000
+ /// 3bit
+ ///
+ internal byte Reserved1 { get; set; } = 0x07;
+ ///
+ /// 与StreamType对应的PID
+ /// 13bit
+ ///
+ public ushort ElementaryPID { get; set; }
+ ///
+ /// 固定为二进制1111(15)
+ /// 1111_0000_0000_0000
+ /// 4bit
+ ///
+ internal byte Reserved2 { get; set; } = 0x0F;
+ ///
+ /// 描述信息,指定为0x000表示没有
+ /// 12bit
+ ///
+ internal ushort ESInfoLength { get; set; } = 0x000;
+
+ public void ToBuffer(ref TSMessagePackWriter writer)
+ {
+ writer.WriteByte((byte)StreamType);
+ writer.WriteUInt16((ushort)(0111_0000_0000_0000| ElementaryPID));
+ writer.WriteUInt16((ushort)(1111_0000_0000_0000| ESInfoLength));
+ }
+ }
+}
diff --git a/src/JT1078.Hls/TS_PMT_Package.cs b/src/JT1078.Hls/TS_PMT_Package.cs
new file mode 100644
index 0000000..7cbae8c
--- /dev/null
+++ b/src/JT1078.Hls/TS_PMT_Package.cs
@@ -0,0 +1,129 @@
+using JT1078.Hls.Formatters;
+using JT1078.Hls.MessagePack;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Hls
+{
+ ///
+ /// 格式节目映射表
+ ///
+ public class TS_PMT_Package : ITSMessagePackFormatter
+ {
+ ///
+ /// PMT表取值随意
+ /// 8bit
+ ///
+ public byte TableId { get; set; } = 0xFF;
+ ///
+ /// 固定为二进制1
+ /// 1bit
+ ///
+ internal byte SectionSyntaxIndicator { get; set; } = 0x01;
+ ///
+ /// 固定为二进制0
+ /// 1bit
+ ///
+ internal byte Zero { get; set; } = 0x00;
+ ///
+ /// 固定为二进制3
+ /// 2bit
+ ///
+ internal byte Reserved1 { get; set; } = 0x03;
+ ///
+ /// 后面数据的长度
+ /// 12bit
+ ///
+ public ushort SectionLength { get; set; }
+ ///
+ /// 频道号码,表示当前的PMT关联到的频道,取值0x0001
+ /// 16bit
+ ///
+ public ushort ProgramNumber { get; set; } = 0x0001;
+ ///
+ /// 固定为二进制3
+ /// 2bit
+ ///
+ internal byte Reserved2 { get; set; } = 0x03;
+ ///
+ /// 版本号,固定为二进制00000,如果PAT有变化则版本号加1
+ /// 5bit
+ ///
+ public byte VersionNumber { get; set; } = 0x00;
+ ///
+ /// 固定为二进制1,表示这个PAT表可以用,如果为0则要等待下一个PAT表
+ /// 1bit
+ ///
+ public byte CurrentNextIndicator { get; set; } = 1;
+ ///
+ /// 固定为0x00
+ /// bit8
+ ///
+ internal byte SectionNumber { get; set; } = 0x00;
+ ///
+ /// 固定为0x00
+ /// bit8
+ ///
+ internal byte LastSectionNumber { get; set; } = 0x00;
+ ///
+ /// 固定为二进制111(7)
+ /// 2bit
+ ///
+ internal byte Reserved3 { get; set; } = 0x07;
+ ///
+ /// PCR(节目参考时钟)所在TS分组的PID,指定为视频PID
+ /// 13bit
+ ///
+ public ushort PCR_PID { get; set; }
+ ///
+ /// 固定为二进制1111(F)
+ /// 4bit
+ ///
+ internal byte Reserved4 { get; set; } = 0x0F;
+ ///
+ /// 节目描述信息,指定为0x000表示没有
+ /// 12bit
+ ///
+ public ushort ProgramInfoLength { get; set; }
+ public List Components { get; set; }
+ ///
+ /// 前面数据的CRC32校验码
+ ///
+ public uint CRC32 { get; set; }
+ public void ToBuffer(ref TSMessagePackWriter writer)
+ {
+ writer.WriteByte(TableId);
+ //SectionSyntaxIndicator Zero Reserved1 SectionLength
+ //1 0 11 0000 0000 0000
+ //(ushort)(1011_0000_0000_0000 | SectionLength)
+ // todo:
+ //writer.WriteUInt16((ushort)(1011_0000_0000_0000 | SectionLength));
+ writer.Skip(2, out int SectionLengthPosition);
+ writer.WriteUInt16(ProgramNumber);
+ //Reserved2 VersionNumber CurrentNextIndicator
+ //11 00000 1
+ var a = Reserved2 & 0xC0;
+ var b = VersionNumber & 0x3E;
+ var c=(byte)(a | b | CurrentNextIndicator);
+ writer.WriteByte(c);
+ writer.WriteByte(SectionNumber);
+ writer.WriteByte(LastSectionNumber);
+ //Reserved3 PCR_PID
+ //111 0000 0000 0000 0
+ writer.WriteUInt16((ushort)(0111_0000_0000_0000 | PCR_PID));
+ //Reserved4 ProgramInfoLength
+ //1111 0000 0000 0000
+ writer.WriteUInt16((ushort)(1111_0000_0000_0000 | ProgramInfoLength));
+ if (Components != null)
+ {
+ foreach(var component in Components)
+ {
+ component.ToBuffer(ref writer);
+ }
+ }
+ writer.WriteUInt16Return((ushort)(1011_0000_0000_0000 | (ushort)(writer.GetCurrentPosition() - SectionLengthPosition - 2)), SectionLengthPosition);
+ writer.WriteCRC32(SectionLengthPosition);
+ }
+ }
+}
diff --git a/src/JT1078.Hls/TS_Package.cs b/src/JT1078.Hls/TS_Package.cs
new file mode 100644
index 0000000..7675b44
--- /dev/null
+++ b/src/JT1078.Hls/TS_Package.cs
@@ -0,0 +1,17 @@
+using JT1078.Protocol.H264;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Hls
+{
+ public class TS_Package
+ {
+ public TS_Header Header { get; set; }
+ public PES_Package Payload { get; set; }
+ ///
+ /// 填充字节,取值0xff
+ ///
+ public byte[] Fill { get; set; }
+ }
+}
diff --git a/src/JT1078.Hls/Util.cs b/src/JT1078.Hls/Util.cs
new file mode 100644
index 0000000..4ad17e3
--- /dev/null
+++ b/src/JT1078.Hls/Util.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Hls
+{
+ class Util
+ {
+ public static uint[] crcTable =
+ {
+ 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
+ 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
+ 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
+ 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
+ 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
+ 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
+ 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
+ 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
+ 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
+ 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
+ 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
+ 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
+ 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
+ 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
+ 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
+ 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
+ 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
+ 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
+ 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
+ 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
+ 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
+ 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
+ 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
+ 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
+ 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
+ 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
+ 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
+ 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
+ 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
+ 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
+ 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
+ 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
+ };
+ }
+}
diff --git a/src/JT1078.sln b/src/JT1078.sln
index 620b8c8..76cb8ef 100644
--- a/src/JT1078.sln
+++ b/src/JT1078.sln
@@ -34,6 +34,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.Flv.Benchmark", "JT1
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.Hls", "JT1078.Hls\JT1078.Hls.csproj", "{C98AD4CE-D7F5-4F7F-BAB5-D1AD50DDF14F}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.Hls.Test", "JT1078.Hls.Test\JT1078.Hls.Test.csproj", "{5564C20B-BFF4-4A2A-BDF2-C7427E93E993}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -88,6 +90,10 @@ Global
{C98AD4CE-D7F5-4F7F-BAB5-D1AD50DDF14F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C98AD4CE-D7F5-4F7F-BAB5-D1AD50DDF14F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C98AD4CE-D7F5-4F7F-BAB5-D1AD50DDF14F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5564C20B-BFF4-4A2A-BDF2-C7427E93E993}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5564C20B-BFF4-4A2A-BDF2-C7427E93E993}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5564C20B-BFF4-4A2A-BDF2-C7427E93E993}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5564C20B-BFF4-4A2A-BDF2-C7427E93E993}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -98,6 +104,7 @@ Global
{9ADD82F9-E0B2-4263-8573-151F673BB33F} = {0655AF84-E578-409F-AB0E-B47E0D2F6814}
{9DB37370-AC73-434B-9CE2-6659321858C8} = {0655AF84-E578-409F-AB0E-B47E0D2F6814}
{D13FE092-1D11-4545-A322-9F06BCDAC0FD} = {0655AF84-E578-409F-AB0E-B47E0D2F6814}
+ {5564C20B-BFF4-4A2A-BDF2-C7427E93E993} = {0655AF84-E578-409F-AB0E-B47E0D2F6814}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FAE1656D-226F-4B4B-8C33-615D7E632B26}