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}