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()}"; + } } }