diff --git a/.gitignore b/.gitignore index c1cd75f..1959596 100644 --- a/.gitignore +++ b/.gitignore @@ -330,3 +330,4 @@ ASALocalRun/ .mfractor/ /src/JT1078.Flv.Test/H264/JT1078_3.txt /src/JT1078.Flv.Test/H264/JT1078_4.txt +/src/JT1078.Flv.Test/H264/JT1078_5.txt diff --git a/src/JT1078.Flv.Test/FlvEncoderTest.cs b/src/JT1078.Flv.Test/FlvEncoderTest.cs index d9ae7e1..eb04845 100644 --- a/src/JT1078.Flv.Test/FlvEncoderTest.cs +++ b/src/JT1078.Flv.Test/FlvEncoderTest.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using JT1078.Protocol.Enums; using JT1078.Flv.H264; +using JT1078.Flv.MessagePack; namespace JT1078.Flv.Test { @@ -208,5 +209,67 @@ namespace JT1078.Flv.Test fileStream?.Dispose(); } } + + [Fact] + public void FlvEncoder_Test_5() + { + FileStream fileStream = null; + Flv.H264.H264Decoder decoder = new Flv.H264.H264Decoder(); + List h264NALULs = new List(); + FlvEncoder encoder = new FlvEncoder(); + try + { + var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_5.flv"); + var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_5.txt")); + if (File.Exists(filepath)) + { + File.Delete(filepath); + } + + JT1078Package Package = null; + + foreach (var line in lines) + { + var data = line.Split(','); + var bytes = data[6].ToHexBytes(); + JT1078Package package = JT1078Serializer.Deserialize(bytes); + Package = JT1078Serializer.Merge(package); + if (Package != null) + { + var tmp = decoder.ParseNALU(Package); + if (tmp != null && tmp.Count > 0) + { + h264NALULs = h264NALULs.Concat(tmp).ToList(); + } + } + } + var tmp1 = h264NALULs.Where(w => w.NALUHeader.NalUnitType == 7).ToList(); + List tmpSpss = new List(); + foreach (var item in tmp1) + { + ExpGolombReader expGolombReader = new ExpGolombReader(item.RawData); + tmpSpss.Add(expGolombReader.ReadSPS()); + } + fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); + var totalPage = (h264NALULs.Count + 10 - 1) / 10; + for (var i = 0; i < totalPage; i++) + { + var flv2 = encoder.CreateFlvFrame(h264NALULs.Skip(i * 10).Take(10).ToList()); + if (flv2.Length != 0) + { + fileStream.Write(flv2); + } + } + } + catch (Exception ex) + { + Assert.Throws(() => { }); + } + finally + { + fileStream?.Close(); + fileStream?.Dispose(); + } + } } } diff --git a/src/JT1078.Flv.Test/H264/JT1078_5.rar b/src/JT1078.Flv.Test/H264/JT1078_5.rar new file mode 100644 index 0000000..7a23fbb Binary files /dev/null and b/src/JT1078.Flv.Test/H264/JT1078_5.rar differ diff --git a/src/JT1078.Flv.Test/H264/index.html b/src/JT1078.Flv.Test/H264/index.html index 62bfaf2..e67b143 100644 --- a/src/JT1078.Flv.Test/H264/index.html +++ b/src/JT1078.Flv.Test/H264/index.html @@ -8,27 +8,21 @@ - diff --git a/src/JT1078.Flv.Test/JT1078.Flv.Test.csproj b/src/JT1078.Flv.Test/JT1078.Flv.Test.csproj index d7c94af..6afd203 100644 --- a/src/JT1078.Flv.Test/JT1078.Flv.Test.csproj +++ b/src/JT1078.Flv.Test/JT1078.Flv.Test.csproj @@ -7,6 +7,7 @@ + @@ -39,6 +40,9 @@ Always + + Always + diff --git a/src/JT1078.Flv/FlvEncoder.cs b/src/JT1078.Flv/FlvEncoder.cs index d2e5370..5e0af70 100644 --- a/src/JT1078.Flv/FlvEncoder.cs +++ b/src/JT1078.Flv/FlvEncoder.cs @@ -17,6 +17,7 @@ namespace JT1078.Flv private const uint PreviousTagSizeFixedLength = 4; private static readonly ConcurrentDictionary PreviousTagSizeDict; private static readonly ConcurrentDictionary FrameInitDict; + private static readonly ConcurrentDictionary VideoSPSDict; private static readonly Flv.H264.H264Decoder H264Decoder; static FlvEncoder() { @@ -24,6 +25,7 @@ namespace JT1078.Flv VideoFlvHeaderBuffer = VideoFlvHeader.ToArray().ToArray(); PreviousTagSizeDict = new ConcurrentDictionary(); FrameInitDict = new ConcurrentDictionary(); + VideoSPSDict = new ConcurrentDictionary(); H264Decoder = new Flv.H264.H264Decoder(); } /// @@ -32,7 +34,7 @@ namespace JT1078.Flv /// NalUnitType->7 /// NalUnitType->8 /// - public byte[] CreateFlvFirstFrame(H264NALU sps, H264NALU pps) + public byte[] CreateFlvFirstFrame(H264NALU sps, H264NALU pps,bool isSendFlvHeader=true,uint previousTagSize=0) { byte[] buffer = FlvArrayPool.Rent(65535); int currentMarkPosition = 0; @@ -40,16 +42,18 @@ namespace JT1078.Flv try { FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); - //flv header - flvMessagePackWriter.WriteArray(VideoFlvHeaderBuffer); - + if (isSendFlvHeader) + { + //flv header + flvMessagePackWriter.WriteArray(VideoFlvHeaderBuffer); + } //SPS -> 7 ExpGolombReader h264GolombReader = new ExpGolombReader(sps.RawData); var spsInfo = h264GolombReader.ReadSPS(); currentMarkPosition = flvMessagePackWriter.GetCurrentPosition(); //flv body script tag - CreateScriptTagFrame(ref flvMessagePackWriter, spsInfo.width, spsInfo.height); + CreateScriptTagFrame(ref flvMessagePackWriter, spsInfo.width, spsInfo.height, previousTagSize); nextMarkPosition = flvMessagePackWriter.GetCurrentPosition(); //flv body video tag @@ -78,10 +82,10 @@ namespace JT1078.Flv FlvArrayPool.Return(buffer); } } - private void CreateScriptTagFrame(ref FlvMessagePackWriter flvMessagePackWriter, int width, int height,double frameRate = 25d) + private void CreateScriptTagFrame(ref FlvMessagePackWriter flvMessagePackWriter, int width, int height,uint previousTagSize, double frameRate = 25d) { //flv body PreviousTagSize awalys 0 - flvMessagePackWriter.WriteUInt32(0); + flvMessagePackWriter.WriteUInt32(previousTagSize); //flv body script tag //flv body tag header FlvTags flvTags = new FlvTags(); @@ -141,7 +145,7 @@ namespace JT1078.Flv public static uint LastFrameInterval = 0; - private void CreateVideoTagOtherFrame(ref FlvMessagePackWriter flvMessagePackWriter, uint previousTagSize, H264NALU nALU, H264NALU sps, H264NALU pps,H264NALU sei) + private void CreateVideoTagOtherFrame(ref FlvMessagePackWriter flvMessagePackWriter, uint previousTagSize, H264NALU nALU) { //flv body PreviousTagSize flvMessagePackWriter.WriteUInt32(previousTagSize); @@ -156,38 +160,18 @@ namespace JT1078.Flv flvTags.VideoTagsData.VideoData = new AvcVideoPacke(); flvTags.VideoTagsData.VideoData.CompositionTime = 0; flvTags.VideoTagsData.VideoData.AvcPacketType = AvcPacketType.Raw; - flvTags.VideoTagsData.VideoData.MultiData = new List(); LastFrameInterval += nALU.LastFrameInterval; flvTags.Timestamp = LastFrameInterval; - if (nALU.NALUHeader.NalUnitType == 5) { flvTags.VideoTagsData.FrameType = FrameType.KeyFrame; - if (sps!=null && pps != null) - { - //flvTags.VideoTagsData.VideoData.MultiData.Add(sps.RawData); - //flvTags.VideoTagsData.VideoData.MultiData.Add(pps.RawData); - flvTags.VideoTagsData.VideoData.MultiData.Add(nALU.RawData); - } - else - { - //if (sei != null) - //{ - // flvTags.VideoTagsData.VideoData.MultiData.Add(sei.RawData); - //} - flvTags.VideoTagsData.VideoData.MultiData.Add(nALU.RawData); - } } else { flvTags.VideoTagsData.FrameType = FrameType.InterFrame; - //if (sei != null) - //{ - // flvTags.VideoTagsData.VideoData.MultiData.Add(sei.RawData); - //} - flvTags.VideoTagsData.VideoData.MultiData.Add(nALU.RawData); } + flvTags.VideoTagsData.VideoData.Data = nALU.RawData; flvMessagePackWriter.WriteFlvTag(flvTags); } public byte[] CreateFlvFrame(List nALUs, int minimumLength = 65535) @@ -204,10 +188,22 @@ namespace JT1078.Flv if (sps!=null && pps != null) { string key = sps.GetKey(); - if (!FrameInitDict.ContainsKey(key)) + ExpGolombReader h264GolombReader = new ExpGolombReader(sps.RawData); + var spsInfo = h264GolombReader.ReadSPS(); + if(VideoSPSDict.TryGetValue(key,out var spsInfoCache)) + { + if(spsInfoCache.height!= spsInfo.height && spsInfoCache.width!= spsInfo.width) + { + uint previousTagSize = 0; + PreviousTagSizeDict.TryGetValue(key, out previousTagSize); + flvMessagePackWriter.WriteArray(CreateFlvFirstFrame(sps, pps,false, previousTagSize)); + VideoSPSDict.TryUpdate(key, spsInfo, spsInfo); + } + } + else { flvMessagePackWriter.WriteArray(CreateFlvFirstFrame(sps, pps)); - FrameInitDict.TryAdd(key, true); + VideoSPSDict.TryAdd(key, spsInfo); } } foreach (var naln in nALUs.Where(w=> w.NALUHeader.NalUnitType != 7 && w.NALUHeader.NalUnitType != 8 && w.NALUHeader.NalUnitType != 6)) @@ -216,7 +212,7 @@ namespace JT1078.Flv if (PreviousTagSizeDict.TryGetValue(key, out uint previousTagSize)) { currentMarkPosition = flvMessagePackWriter.GetCurrentPosition(); - CreateVideoTagOtherFrame(ref flvMessagePackWriter, previousTagSize, naln,sps, pps, sei); + CreateVideoTagOtherFrame(ref flvMessagePackWriter, previousTagSize, naln); nextMarkPosition = flvMessagePackWriter.GetCurrentPosition(); uint tmpPreviousTagSize = (uint)(nextMarkPosition - currentMarkPosition - PreviousTagSizeFixedLength); PreviousTagSizeDict.TryUpdate(key, tmpPreviousTagSize, previousTagSize); diff --git a/src/JT1078.Flv/MessagePack/EXPGolombReader.cs b/src/JT1078.Flv/MessagePack/EXPGolombReader.cs index 2a91f18..7c2cd46 100644 --- a/src/JT1078.Flv/MessagePack/EXPGolombReader.cs +++ b/src/JT1078.Flv/MessagePack/EXPGolombReader.cs @@ -21,7 +21,7 @@ namespace JT1078.Flv.MessagePack Word = 0; BitsAvailable = 0; } - public (byte profileIdc,byte levelIdc,uint profileCompat,int width, int height) ReadSPS() + public SPSInfo ReadSPS() { int sarScale = 1; uint frameCropLeftOffset=0; @@ -153,7 +153,7 @@ namespace JT1078.Flv.MessagePack } 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); + return new SPSInfo { profileIdc= profileIdc, levelIdc= levelIdc, profileCompat= profileCompat, width= width, height= height }; } public void LoadWord() { @@ -310,4 +310,13 @@ namespace JT1078.Flv.MessagePack } } } + + public struct SPSInfo + { + public byte profileIdc { get; set; } + public byte levelIdc { get; set; } + public uint profileCompat { get; set; } + public int width { get; set; } + public int height { get; set; } + } } diff --git a/src/JT1078.Flv/MessagePack/FlvMessagePackWriter_Flv.cs b/src/JT1078.Flv/MessagePack/FlvMessagePackWriter_Flv.cs index e289158..39f5552 100644 --- a/src/JT1078.Flv/MessagePack/FlvMessagePackWriter_Flv.cs +++ b/src/JT1078.Flv/MessagePack/FlvMessagePackWriter_Flv.cs @@ -85,19 +85,6 @@ namespace JT1078.Flv.MessagePack else if(videoPacke.AvcPacketType == AvcPacketType.Raw) { WriteUInt24(videoPacke.CompositionTime); - //One or more NALUs - //WriteArray(new byte[] {0,0,0,1}); - if (videoPacke.MultiData != null) - { - foreach(var item in videoPacke.MultiData) - { - if (item != null && item.Length > 0) - { - WriteInt32(item.Length); - WriteArray(item); - } - } - } if (videoPacke.Data != null && videoPacke.Data.Length>0) { WriteInt32(videoPacke.Data.Length); @@ -121,19 +108,10 @@ namespace JT1078.Flv.MessagePack //reserved(6bits)+LengthSizeMinusOne(2bits) WriteByte(0xFF); WriteByte((byte)configurationRecord.NumOfSequenceParameterSets); - WriteUInt16((ushort)(configurationRecord.SPSBuffer.Length)); - - //WriteUInt16((ushort)(configurationRecord.SPSBuffer.Length + 4)); - //WriteArray(new byte[] { 0, 0, 0, 1 }); - WriteArray(configurationRecord.SPSBuffer); WriteByte(configurationRecord.NumOfPictureParameterSets); - WriteUInt16((ushort)(configurationRecord.PPSBuffer.Length)); - - //WriteUInt16((ushort)(configurationRecord.PPSBuffer.Length+4)); - //WriteArray(new byte[] { 0, 0, 0, 1 }); WriteArray(configurationRecord.PPSBuffer); } } diff --git a/src/JT1078.Flv/Metadata/AvcVideoPacke.cs b/src/JT1078.Flv/Metadata/AvcVideoPacke.cs index d88406f..b93a14c 100644 --- a/src/JT1078.Flv/Metadata/AvcVideoPacke.cs +++ b/src/JT1078.Flv/Metadata/AvcVideoPacke.cs @@ -14,7 +14,5 @@ namespace JT1078.Flv.Metadata public AVCDecoderConfigurationRecord AVCDecoderConfiguration { get; set; } public byte[] Data { get; set; } - - public List MultiData { get; set; } } } diff --git a/src/JT1078.Protocol/MessagePack/JT1078MessagePackWriter.cs b/src/JT1078.Protocol/MessagePack/JT1078MessagePackWriter.cs index 65182f9..ba3b191 100644 --- a/src/JT1078.Protocol/MessagePack/JT1078MessagePackWriter.cs +++ b/src/JT1078.Protocol/MessagePack/JT1078MessagePackWriter.cs @@ -24,40 +24,22 @@ namespace JT1078.Protocol.MessagePack } public void WriteUInt16(ushort value) { - var span = writer.Free; - span[0] = (byte)(value >> 8); - span[1] = (byte)value; + BinaryPrimitives.WriteUInt16BigEndian(writer.Free, value); writer.Advance(2); } public void WriteInt32(int value) { - var span = writer.Free; - span[0] = (byte)(value >> 24); - span[1] = (byte)(value >> 16); - span[2] = (byte)(value >> 8); - span[3] = (byte)value; + BinaryPrimitives.WriteInt32BigEndian(writer.Free, value); writer.Advance(4); } public void WriteUInt64(ulong value) { - var span = writer.Free; - span[0] = (byte)(value >> 56); - span[1] = (byte)(value >> 48); - span[2] = (byte)(value >> 40); - span[3] = (byte)(value >> 32); - span[4] = (byte)(value >> 24); - span[5] = (byte)(value >> 16); - span[6] = (byte)(value >> 8); - span[7] = (byte)value; + BinaryPrimitives.WriteUInt64BigEndian(writer.Free, value); writer.Advance(8); } public void WriteUInt32(uint value) { - var span = writer.Free; - span[0] = (byte)(value >> 24); - span[1] = (byte)(value >> 16); - span[2] = (byte)(value >> 8); - span[3] = (byte)value; + BinaryPrimitives.WriteUInt32BigEndian(writer.Free, value); writer.Advance(4); } public void WriteArray(ReadOnlySpan src)