From 6547c267844303dada5f5bd628e0982a9337a1c6 Mon Sep 17 00:00:00 2001 From: yedajiang44 <602830483@qq.com> Date: Wed, 8 Jan 2020 12:15:02 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E6=8E=89=E6=97=A7?= =?UTF-8?q?=E7=89=88flv=E7=BC=96=E7=A0=81=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JT1078FlvEncoderContext.cs | 9 - src/JT1078.Flv.Test/FlvEncoderTest.cs | 518 +++++++++--------- .../JT1078FlvEncoderContext.cs | 16 +- .../JT1078SerializerTest.cs | 4 +- 4 files changed, 269 insertions(+), 278 deletions(-) diff --git a/src/JT1078.Flv.Benchmark/JT1078FlvEncoderContext.cs b/src/JT1078.Flv.Benchmark/JT1078FlvEncoderContext.cs index 8fd9b31..4d46404 100644 --- a/src/JT1078.Flv.Benchmark/JT1078FlvEncoderContext.cs +++ b/src/JT1078.Flv.Benchmark/JT1078FlvEncoderContext.cs @@ -63,15 +63,6 @@ namespace JT1078.Flv.Benchmark var nalus = h264Decoder.ParseNALU(Package); } } - - [Benchmark(Description = "FlvEncoder")] - public void FlvEncoder() - { - for(var i=0;i< N;i++) - { - var contents = flvEncoder.CreateFlvFrame(H264NALUs); - } - } } public class JT1078FlvEncoderConfig : ManualConfig diff --git a/src/JT1078.Flv.Test/FlvEncoderTest.cs b/src/JT1078.Flv.Test/FlvEncoderTest.cs index cf7dded..ea95ae0 100644 --- a/src/JT1078.Flv.Test/FlvEncoderTest.cs +++ b/src/JT1078.Flv.Test/FlvEncoderTest.cs @@ -37,289 +37,289 @@ namespace JT1078.Flv.Test Assert.Equal(4, nalus.Count); FlvEncoder encoder = new FlvEncoder(); - var contents = encoder.CreateFlvFrame(nalus); var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_1.flv"); if (File.Exists(filepath)) { File.Delete(filepath); } FileStream fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); - fileStream.Write(FlvEncoder.VideoFlvHeaderBuffer); - fileStream.Write(contents); + fileStream.Write(encoder.EncoderFlvHeader()); + fileStream.Write(encoder.EncoderScriptTag()); + fileStream.Write(encoder.EncoderVideoTag(Package, true)); fileStream.Close(); } - [Fact] - public void 测试前几帧的数据() - { - FileStream fileStream=null; - try - { - JT1078Package Package = null; - List h264NALULs = new List(); - H264Decoder decoder = new H264Decoder(); - FlvEncoder encoder = new FlvEncoder(); - var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_2.txt")); - 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(); - } - } - } + //[Fact] + //public void 测试前几帧的数据() + //{ + // FileStream fileStream = null; + // try + // { + // JT1078Package Package = null; + // List h264NALULs = new List(); + // H264Decoder decoder = new H264Decoder(); + // FlvEncoder encoder = new FlvEncoder(); + // var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_2.txt")); + // 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 filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_2.flv"); - if (File.Exists(filepath)) - { - File.Delete(filepath); - } - fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); - fileStream.Write(FlvEncoder.VideoFlvHeaderBuffer); - 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) - { + // var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_2.flv"); + // if (File.Exists(filepath)) + // { + // File.Delete(filepath); + // } + // fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); + // fileStream.Write(FlvEncoder.VideoFlvHeaderBuffer); + // 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) + // { - } - finally - { - fileStream?.Close(); - fileStream?.Dispose(); - } - } + // } + // finally + // { + // fileStream?.Close(); + // fileStream?.Dispose(); + // } + //} - [Fact] - public void 测试可以播放的Flv1() - { - FileStream fileStream = null; - H264Decoder decoder = new H264Decoder(); - List h264NALULs = new List(); - FlvEncoder encoder = new FlvEncoder(); - try - { - var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_3.flv"); - var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_3.txt")); - if (File.Exists(filepath)) - { - File.Delete(filepath); - } + //[Fact] + //public void 测试可以播放的Flv1() + //{ + // FileStream fileStream = null; + // H264Decoder decoder = new H264Decoder(); + // List h264NALULs = new List(); + // FlvEncoder encoder = new FlvEncoder(); + // try + // { + // var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_3.flv"); + // var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_3.txt")); + // if (File.Exists(filepath)) + // { + // File.Delete(filepath); + // } - JT1078Package Package = null; + // 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(); - } - } - } + // 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(); + // } + // } + // } - fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); - fileStream.Write(FlvEncoder.VideoFlvHeaderBuffer); - 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(); - } - } + // fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); + // fileStream.Write(FlvEncoder.VideoFlvHeaderBuffer); + // 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(); + // } + //} - [Fact] - public void 测试可以播放的Flv2() - { - FileStream fileStream = null; - H264Decoder decoder = new H264Decoder(); - List h264NALULs = new List(); - FlvEncoder encoder = new FlvEncoder(); - try - { - var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_4.flv"); - var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_4.txt")); - if (File.Exists(filepath)) - { - File.Delete(filepath); - } + //[Fact] + //public void 测试可以播放的Flv2() + //{ + // FileStream fileStream = null; + // H264Decoder decoder = new H264Decoder(); + // List h264NALULs = new List(); + // FlvEncoder encoder = new FlvEncoder(); + // try + // { + // var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_4.flv"); + // var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_4.txt")); + // if (File.Exists(filepath)) + // { + // File.Delete(filepath); + // } - JT1078Package Package = null; + // 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(); - } - } - } + // 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(); + // } + // } + // } - fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); - fileStream.Write(FlvEncoder.VideoFlvHeaderBuffer); - 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(); - } - } + // fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); + // fileStream.Write(FlvEncoder.VideoFlvHeaderBuffer); + // 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(); + // } + //} - [Fact] - public void 测试主次码流切换() - { - FileStream fileStream = null; - H264Decoder decoder = new 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); - } + //[Fact] + //public void 测试主次码流切换() + //{ + // FileStream fileStream = null; + // H264Decoder decoder = new 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; + // 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(); - List times = new List(); - List lastIFrameIntervals = new List(); - List lastFrameIntervals = new List(); - List type = new List(); - foreach (var item in h264NALULs) - { - //type.Add(item.NALUHeader.NalUnitType); - times.Add(item.Timestamp); - lastFrameIntervals.Add(item.LastFrameInterval); - lastIFrameIntervals.Add(item.LastIFrameInterval); - if(item.NALUHeader.NalUnitType == 7) - { - 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(); - } - } + // 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(); + // List times = new List(); + // List lastIFrameIntervals = new List(); + // List lastFrameIntervals = new List(); + // List type = new List(); + // foreach (var item in h264NALULs) + // { + // //type.Add(item.NALUHeader.NalUnitType); + // times.Add(item.Timestamp); + // lastFrameIntervals.Add(item.LastFrameInterval); + // lastIFrameIntervals.Add(item.LastIFrameInterval); + // if (item.NALUHeader.NalUnitType == 7) + // { + // 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(); + // } + //} - [Fact] - public void CreateScriptTagFrameTest() - { - FlvEncoder flvEncoder = new FlvEncoder(); - var hexData = flvEncoder.CreateScriptTagFrame(288, 352); - Assert.Equal(151, hexData.Length); - } + //[Fact] + //public void CreateScriptTagFrameTest() + //{ + // FlvEncoder flvEncoder = new FlvEncoder(); + // var hexData = flvEncoder.CreateScriptTagFrame(288, 352); + // Assert.Equal(151, hexData.Length); + //} - [Fact] - public void CreateVideoTag0FrameTest() - { - FlvEncoder flvEncoder = new FlvEncoder(); - var hexData = flvEncoder.CreateVideoTag0Frame( - new byte[] { 0x67, 0x4D, 0, 0x14, 0x95, 0xA8, 0x58, 0x25, 0x90 }, - new byte[] { 0x68, 0xEE, 0x3C, 0x80 }, - new SPSInfo { levelIdc = 0x14, profileIdc= 0x4d, profileCompat=0 }); - Assert.Equal(40, hexData.Length); - } + //[Fact] + //public void CreateVideoTag0FrameTest() + //{ + // FlvEncoder flvEncoder = new FlvEncoder(); + // var hexData = flvEncoder.CreateVideoTag0Frame( + // new byte[] { 0x67, 0x4D, 0, 0x14, 0x95, 0xA8, 0x58, 0x25, 0x90 }, + // new byte[] { 0x68, 0xEE, 0x3C, 0x80 }, + // new SPSInfo { levelIdc = 0x14, profileIdc = 0x4d, profileCompat = 0 }); + // Assert.Equal(40, hexData.Length); + //} - [Fact] - public void GetFirstFlvFrameTest() - { - FlvEncoder flvEncoder = new FlvEncoder(); - string key = "test"; - var bufferFlvFrame = new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE, 0xF }; - FlvEncoder.FirstFlvFrameCache.TryAdd(key, (2, new byte[] { 1, 2, 3, 4, 5, 6 },true)); - var buffer=flvEncoder.GetFirstFlvFrame(key, bufferFlvFrame); - //替换PreviousTagSize 4位的长度为首帧的 PreviousTagSize - Assert.Equal(new byte[] { 1, 2, 3, 4, 5, 6, 0, 0, 0, 2, 0xE, 0xF }, buffer); - } + //[Fact] + //public void GetFirstFlvFrameTest() + //{ + // FlvEncoder flvEncoder = new FlvEncoder(); + // string key = "test"; + // var bufferFlvFrame = new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE, 0xF }; + // FlvEncoder.FirstFlvFrameCache.TryAdd(key, (2, new byte[] { 1, 2, 3, 4, 5, 6 }, true)); + // var buffer = flvEncoder.GetFirstFlvFrame(key, bufferFlvFrame); + // //替换PreviousTagSize 4位的长度为首帧的 PreviousTagSize + // Assert.Equal(new byte[] { 1, 2, 3, 4, 5, 6, 0, 0, 0, 2, 0xE, 0xF }, buffer); + //} } } diff --git a/src/JT1078.Protocol.Benchmark/JT1078FlvEncoderContext.cs b/src/JT1078.Protocol.Benchmark/JT1078FlvEncoderContext.cs index 8fd9b31..a7a35f0 100644 --- a/src/JT1078.Protocol.Benchmark/JT1078FlvEncoderContext.cs +++ b/src/JT1078.Protocol.Benchmark/JT1078FlvEncoderContext.cs @@ -64,14 +64,14 @@ namespace JT1078.Flv.Benchmark } } - [Benchmark(Description = "FlvEncoder")] - public void FlvEncoder() - { - for(var i=0;i< N;i++) - { - var contents = flvEncoder.CreateFlvFrame(H264NALUs); - } - } + //[Benchmark(Description = "FlvEncoder")] + //public void FlvEncoder() + //{ + // for(var i=0;i< N;i++) + // { + // var contents = flvEncoder.CreateFlvFrame(H264NALUs); + // } + //} } public class JT1078FlvEncoderConfig : ManualConfig diff --git a/src/JT1078.Protocol.Test/JT1078SerializerTest.cs b/src/JT1078.Protocol.Test/JT1078SerializerTest.cs index 484a712..582d7d3 100644 --- a/src/JT1078.Protocol.Test/JT1078SerializerTest.cs +++ b/src/JT1078.Protocol.Test/JT1078SerializerTest.cs @@ -274,7 +274,7 @@ namespace JT1078.Protocol.Test JT1078Label2 label2 = new JT1078Label2(254); Assert.Equal(254, label2.ToByte()); Assert.Equal(1, label2.M); - Assert.Equal(126, label2.PT); + Assert.Equal(126, (byte)label2.PT); } [Fact] @@ -283,7 +283,7 @@ namespace JT1078.Protocol.Test JT1078Label2 label2 = new JT1078Label2(0, 28); Assert.Equal(28, label2.ToByte()); Assert.Equal(0, label2.M); - Assert.Equal(28, label2.PT); + Assert.Equal(Jt1078AudioType.AMR, label2.PT); } [Fact] From 6c0cc80bb3f1e61f5aa99f7490c9e369c5cd12f6 Mon Sep 17 00:00:00 2001 From: yedajiang44 <602830483@qq.com> Date: Wed, 8 Jan 2020 12:15:17 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E9=87=8D=E6=9E=84flv=E7=BC=96=E7=A0=81?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/JT1078.Flv/Audio/FaacEncoder.cs | 6 +- src/JT1078.Flv/Extensions/Amf3Extensions.cs | 5 + src/JT1078.Flv/FlvEncoder.cs | 592 ++++++++---------- src/JT1078.Flv/JT1078.Flv.xml | 107 ++-- .../Metadata/Amf3Metadata_AudioCodecId.cs | 27 + .../Metadata/Amf3Metadata_AudioSampleRate.cs | 27 + .../Metadata/Amf3Metadata_AudioSampleSize.cs | 27 + .../Metadata/Amf3Metadata_AudioStereo.cs | 27 + src/JT1078.Protocol/Enums/Jt1078AudioType.cs | 41 ++ src/JT1078.Protocol/JT1078Label2.cs | 22 +- 10 files changed, 496 insertions(+), 385 deletions(-) create mode 100644 src/JT1078.Flv/Metadata/Amf3Metadata_AudioCodecId.cs create mode 100644 src/JT1078.Flv/Metadata/Amf3Metadata_AudioSampleRate.cs create mode 100644 src/JT1078.Flv/Metadata/Amf3Metadata_AudioSampleSize.cs create mode 100644 src/JT1078.Flv/Metadata/Amf3Metadata_AudioStereo.cs create mode 100644 src/JT1078.Protocol/Enums/Jt1078AudioType.cs diff --git a/src/JT1078.Flv/Audio/FaacEncoder.cs b/src/JT1078.Flv/Audio/FaacEncoder.cs index 50a58ef..6372a60 100644 --- a/src/JT1078.Flv/Audio/FaacEncoder.cs +++ b/src/JT1078.Flv/Audio/FaacEncoder.cs @@ -14,7 +14,7 @@ namespace JT1078.Flv.Audio private readonly int maxOutput; public readonly int frameSize; private List frameCache = new List(); - public FaacEncoder(int sampleRate, int channels, int sampleBit) + public FaacEncoder(int sampleRate, int channels, int sampleBit, bool adts = false) { var inputSampleBytes = new byte[4]; var maxOutputBytes = new byte[4]; @@ -27,7 +27,7 @@ namespace JT1078.Flv.Audio var ptr = FaacEncGetCurrentConfiguration(faacEncHandle); var configuration = InteropExtensions.IntPtrToStruct(ptr); configuration.inputFormat = 1; - configuration.outputFormat = 0; + configuration.outputFormat = adts ? 1 : 0; configuration.useTns = 0; configuration.useLfe = 0; configuration.aacObjectType = 2; @@ -97,7 +97,7 @@ namespace JT1078.Flv.Audio [DllImport(DLLFile, EntryPoint = "faacEncEncode", CallingConvention = CallingConvention.StdCall)] private extern static int FaacEncEncode(IntPtr hEncoder, byte[] inputBuffer, int samplesInput, byte[] outputBuffer, int bufferSize); - [DllImport(DLLFile, EntryPoint= "faacEncClose", CallingConvention = CallingConvention.StdCall)] + [DllImport(DLLFile, EntryPoint = "faacEncClose", CallingConvention = CallingConvention.StdCall)] //int FAACAPI faacEncClose(faacEncHandle hEncoder); private extern static IntPtr FaacEncClose(IntPtr hEncoder); diff --git a/src/JT1078.Flv/Extensions/Amf3Extensions.cs b/src/JT1078.Flv/Extensions/Amf3Extensions.cs index 5ae1d1d..f2d70d7 100644 --- a/src/JT1078.Flv/Extensions/Amf3Extensions.cs +++ b/src/JT1078.Flv/Extensions/Amf3Extensions.cs @@ -15,5 +15,10 @@ namespace JT1078.Flv.Extensions flvBuffer.Reverse(); flvBuffer.CopyTo(value); } + public static void WriteBool(this IAmf3Metadata metadata, Span value) + { + var flvBuffer = BitConverter.GetBytes(Convert.ToBoolean(metadata.Value)).AsSpan(); + flvBuffer.CopyTo(value); + } } } diff --git a/src/JT1078.Flv/FlvEncoder.cs b/src/JT1078.Flv/FlvEncoder.cs index 1a69f3a..3ffc202 100644 --- a/src/JT1078.Flv/FlvEncoder.cs +++ b/src/JT1078.Flv/FlvEncoder.cs @@ -1,194 +1,115 @@ using JT1078.Flv.Enums; -using JT1078.Flv.Extensions; using JT1078.Flv.MessagePack; using JT1078.Flv.Metadata; -using JT1078.Protocol; using JT1078.Protocol.Enums; using JT1078.Protocol.H264; using JT1078.Protocol.MessagePack; -using Microsoft.Extensions.Logging; +using JT1078.Flv.Audio; using System; -using System.Buffers.Binary; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Text; +using JT1078.Protocol; [assembly: InternalsVisibleTo("JT1078.Flv.Test")] namespace JT1078.Flv { - public class FlvEncoder + /// + /// Flv编码器 + /// 一个客户端对应一个实例 + /// + /// 当实例不适用时,尽量手动调用下 + /// + /// + /// 手动编码 + /// 1、 + /// 2、 + /// 3、 + /// 4、 + /// 5、第二个参数传false + /// 6、第二个参数传false + /// 自动编码 + /// 1、 + /// 2、 + /// 3、第二个参数传true + /// 4、第二个参数传true + /// + public class FlvEncoder : IDisposable { - /// - /// Flv固定头部数据 - /// - public static readonly byte[] VideoFlvHeaderBuffer; - private static readonly H264Decoder H264Decoder; - private static readonly ConcurrentDictionary VideoSPSDict; - private static readonly ConcurrentDictionary FlvFrameInfoDict; - internal static readonly ConcurrentDictionary FirstFlvFrameCache; - private readonly ILogger logger; - static FlvEncoder() + uint previousTagSize; + FlvHeader flvHeader = new FlvHeader(true, true); + readonly FaacEncoder faacEncoder; + readonly H264Decoder h264Decoder = new H264Decoder(); + + public FlvEncoder(int sampleRate = 8000, int channels = 1, int sampleBit = 16, bool adts = false) { - FlvHeader VideoFlvHeader = new FlvHeader(true, false); - VideoFlvHeaderBuffer = VideoFlvHeader.ToArray().ToArray(); - VideoSPSDict = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - FlvFrameInfoDict = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - FirstFlvFrameCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - H264Decoder = new H264Decoder(); + faacEncoder = new FaacEncoder(sampleRate, channels, sampleBit, adts); } - public FlvEncoder() - { - } - public FlvEncoder(ILoggerFactory loggerFactory) + /// + /// 编码flv头 + /// + /// 注意:本方法已写入 + /// + /// + /// + /// + /// + public byte[] EncoderFlvHeader(bool hasVideo = true, bool hasAudio = false) { - logger = loggerFactory.CreateLogger("FlvEncoder"); + previousTagSize = 0; + flvHeader = new FlvHeader(hasVideo, hasAudio); + return flvHeader.ToArray().ToArray(); } /// - /// + /// 编码脚本Tag + /// + /// 注意:本方法已写入 + /// /// - /// - /// 由于获取的SIM卡可能为000000000000,所以如果有替换JT1078Package.GetKey()的值 - /// + /// 视频宽度 + /// 视频高度 + /// 是否含有音频,如果有,则写入音频配置,后来发现即便是有音频,这里给false也没关系 + /// 帧率 /// - public byte[] CreateFlvFrame(List nALUs, string key = null, int minimumLength = 65535) + public byte[] EncoderScriptTag(bool hasAudio = false, double frameRate = 25d) { - byte[] buffer = FlvArrayPool.Rent(minimumLength); + byte[] buffer = FlvArrayPool.Rent(1024); try { FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); - H264NALU sps = null, pps = null, sei = null; - foreach (var naln in nALUs) + //flv body script tag + //flv body tag header + FlvTags flvTags = new FlvTags { - key = key ?? naln.GetKey(); - if (sps != null && pps != null) + Type = TagType.ScriptData, + //flv body tag body + DataTagsData = new Amf3 { - var rawData = H264Decoder.DiscardEmulationPreventionBytes(sps.RawData); - ExpGolombReader h264GolombReader = new ExpGolombReader(rawData); - SPSInfo spsInfo = h264GolombReader.ReadSPS(); - if (VideoSPSDict.TryGetValue(key, out var spsInfoCache)) - { - //切换主次码流 - //根据宽高来判断 - if (spsInfoCache.height != spsInfo.height && spsInfoCache.width != spsInfo.width) - { - if (logger != null) - { - if (logger.IsEnabled(LogLevel.Debug)) - { - logger.LogDebug($"Cache:{spsInfoCache.height}-{spsInfoCache.width},Current:{spsInfo.height}-{spsInfo.width}"); - } - } - VideoSPSDict.TryUpdate(key, spsInfo, spsInfoCache); - if (FlvFrameInfoDict.TryGetValue(key, out FlvFrameInfo flvFrameInfo)) - { - flvFrameInfo.Timestamp = naln.Timestamp; - if (logger != null) - { - if (logger.IsEnabled(LogLevel.Debug)) - { - logger.LogDebug($"Cache:{spsInfoCache.height}-{spsInfoCache.width},Current:{spsInfo.height}-{spsInfo.width}"); - } - } - var secondFlvKeyFrame = CreateFirstFlvKeyFrame(sps.RawData, pps.RawData, spsInfo, flvFrameInfo.PreviousTagSize); - flvMessagePackWriter.WriteArray(secondFlvKeyFrame.Buffer); - flvFrameInfo.PreviousTagSize = secondFlvKeyFrame.PreviousTagSize; - FlvFrameInfoDict.TryUpdate(key, flvFrameInfo, flvFrameInfo); - if (FirstFlvFrameCache.TryGetValue(key, out var firstFlvFrameCacche)) - { - FirstFlvFrameCache.TryUpdate(key, (secondFlvKeyFrame.PreviousTagSize, secondFlvKeyFrame.Buffer, true), firstFlvFrameCacche); - } - } - } - } - else + Amf3Metadatas = new List { - var firstFlvKeyFrame = CreateFirstFlvKeyFrame(sps.RawData, pps.RawData, spsInfo); - flvMessagePackWriter.WriteArray(firstFlvKeyFrame.Buffer); - if (logger != null) - { - if (logger.IsEnabled(LogLevel.Debug)) - { - logger.LogDebug($"Current:{spsInfo.height}-{spsInfo.width}"); - } - } - //cache PreviousTagSize - FlvFrameInfoDict.TryAdd(key, new FlvFrameInfo - { - PreviousTagSize = firstFlvKeyFrame.PreviousTagSize, - Interval = (uint)(pps.Timestamp - sps.Timestamp), - Timestamp = pps.Timestamp, - } - ); - FirstFlvFrameCache.TryAdd(key, (firstFlvKeyFrame.PreviousTagSize, firstFlvKeyFrame.Buffer, false)); - VideoSPSDict.TryAdd(key, spsInfo); + new Amf3Metadata_Duration{Value = 0d}, + new Amf3Metadata_VideoDataRate{Value = 0d}, + new Amf3Metadata_VideoCodecId{Value = 7d}, + new Amf3Metadata_FrameRate{Value = frameRate}, + new Amf3Metadata_Width(), + new Amf3Metadata_Height(), } - sps = null; - pps = null; - continue; - } - if (logger != null) - { - if (logger.IsEnabled(LogLevel.Trace)) - { - logger.LogTrace($"SIM:{naln.SIM},CH:{naln.LogicChannelNumber},{naln.DataType.ToString()},NalUnitType:{naln.NALUHeader.NalUnitType},RawData:{naln.RawData.ToHexString()}"); - } - } - //7 8 6 5 1 1 1 1 7 8 6 5 1 1 1 1 1 7 8 6 5 1 1 1 1 1 - switch (naln.NALUHeader.NalUnitType) - { -#warning 是否需要IDR帧? 每次发送IDR帧? 在测试的时候,是否由于图像变化不大所以不需要IDR帧? - case 5:// IDR - if (FlvFrameInfoDict.TryGetValue(key, out FlvFrameInfo idrInfo)) - { - //当前的1078包与上一包1078的时间戳相减再进行累加 - uint interval = (uint)(naln.Timestamp - idrInfo.Timestamp); - idrInfo.Interval += interval; - idrInfo.Timestamp = naln.Timestamp; - // PreviousTagSize - flvMessagePackWriter.WriteUInt32(idrInfo.PreviousTagSize); - // Data Tag Frame - var flvFrameBuffer = CreateVideoTagOtherFrame(idrInfo, naln, sei); - flvMessagePackWriter.WriteArray(flvFrameBuffer); - idrInfo.PreviousTagSize = (uint)flvFrameBuffer.Length; - idrInfo.LastDataType = naln.DataType; - FlvFrameInfoDict.TryUpdate(key, idrInfo, idrInfo); - } - break; - case 1:// I/P/B - if (FlvFrameInfoDict.TryGetValue(key, out FlvFrameInfo flvFrameInfo)) - { - //当前的1078包与上一包1078的时间戳相减再进行累加 - uint interval = (uint)(naln.Timestamp - flvFrameInfo.Timestamp); - flvFrameInfo.Interval += interval; - flvFrameInfo.Timestamp = naln.Timestamp; - // PreviousTagSize - flvMessagePackWriter.WriteUInt32(flvFrameInfo.PreviousTagSize); - // Data Tag Frame - var flvFrameBuffer = CreateVideoTagOtherFrame(flvFrameInfo, naln, sei); - flvMessagePackWriter.WriteArray(flvFrameBuffer); - flvFrameInfo.PreviousTagSize = (uint)flvFrameBuffer.Length; - flvFrameInfo.LastDataType = naln.DataType; - FlvFrameInfoDict.TryUpdate(key, flvFrameInfo, flvFrameInfo); - } - break; - case 7:// SPS - sps = naln; - break; - case 8:// PPS - pps = naln; - break; - case 6://SEI - sei = naln; - break; - default: - break; } + }; + if (hasAudio) + { + flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_AudioCodecId()); + flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_AudioSampleRate()); + flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_AudioSampleSize()); + flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_AudioStereo()); } - return flvMessagePackWriter.FlushAndGetArray(); + flvMessagePackWriter.WriteUInt32(previousTagSize); + flvMessagePackWriter.WriteFlvTag(flvTags); + var data = flvMessagePackWriter.FlushAndGetArray(); + previousTagSize = (uint)(flvTags.DataSize + 11); + return data; } finally { @@ -197,145 +118,180 @@ namespace JT1078.Flv } /// - /// - /// - /// 完整的1078包 - /// 由于获取的SIM卡可能为000000000000,所以如果有替换JT1078Package.GetKey()的值 - /// 默认65535 - /// - public byte[] CreateFlvFrame(JT1078Package package, string key = null, int minimumLength = 65535) - { - var nalus = H264Decoder.ParseNALU(package); - if (nalus == null || nalus.Count <= 0) return default; - return CreateFlvFrame(nalus, key, minimumLength); - } - /// - /// + /// 编码首帧视频,即videoTag[0] + /// + /// 注意:本方法已写入 + /// /// - /// 设备号+通道号(1111111_1) - /// 当前接收到的flv数据 + /// + /// + /// /// - public byte[] GetFirstFlvFrame(string key, byte[] currentBufferFlvFrame) - { - if (FirstFlvFrameCache.TryGetValue(key, out var firstBuffer)) - { - var length = firstBuffer.Buffer.Length + currentBufferFlvFrame.Length + VideoFlvHeaderBuffer.Length; - byte[] buffer = FlvArrayPool.Rent(length); - try - { - Span tmp = buffer; - VideoFlvHeaderBuffer.CopyTo(tmp); - if (firstBuffer.Changed) - { - //新用户进来需要替换为首包的PreviousTagSize 0 - BinaryPrimitives.WriteUInt32BigEndian(firstBuffer.Buffer, 0); - firstBuffer.Buffer.CopyTo(tmp.Slice(VideoFlvHeaderBuffer.Length)); - //新用户进来需要替换为上一包的PreviousTagSize - BinaryPrimitives.WriteUInt32BigEndian(currentBufferFlvFrame, firstBuffer.PreviousTagSize); - currentBufferFlvFrame.CopyTo(tmp.Slice(VideoFlvHeaderBuffer.Length + firstBuffer.Buffer.Length)); - return tmp.Slice(0, length).ToArray(); - } - else - { - firstBuffer.Buffer.CopyTo(tmp.Slice(VideoFlvHeaderBuffer.Length)); - //新用户进来需要替换为首包的PreviousTagSize - BinaryPrimitives.WriteUInt32BigEndian(currentBufferFlvFrame, firstBuffer.PreviousTagSize); - currentBufferFlvFrame.CopyTo(tmp.Slice(VideoFlvHeaderBuffer.Length + firstBuffer.Buffer.Length)); - return tmp.Slice(0, length).ToArray(); - } - } - finally - { - FlvArrayPool.Return(buffer); - } - } - return default; - } - - internal byte[] CreateScriptTagFrame(int width, int height, double frameRate = 25d) + public byte[] EncoderFirstVideoTag(H264NALU sps, H264NALU pps, H264NALU sei) { - byte[] buffer = FlvArrayPool.Rent(1024); + byte[] buffer = FlvArrayPool.Rent(2048); try { FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); - //flv body script tag + var rawData = h264Decoder.DiscardEmulationPreventionBytes(sps.RawData); + ExpGolombReader h264GolombReader = new ExpGolombReader(rawData); + SPSInfo spsInfo = h264GolombReader.ReadSPS(); + //flv body video tag //flv body tag header - FlvTags flvTags = new FlvTags(); - flvTags.Type = TagType.ScriptData; - flvTags.Timestamp = 0; - flvTags.TimestampExt = 0; - flvTags.StreamId = 0; - //flv body tag body - flvTags.DataTagsData = new Amf3(); - flvTags.DataTagsData.Amf3Metadatas = new List(); - flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_Duration - { - Value = 0d - }); - flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_VideoDataRate + FlvTags flvTags = new FlvTags { - Value = 0d - }); - flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_VideoCodecId - { - Value = 7d - }); - flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_FrameRate - { - Value = frameRate - }); - flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_Width + Type = TagType.Video, + Timestamp = (uint)sps.Timestamp, + TimestampExt = 0, + StreamId = 0, + //flv body tag body + VideoTagsData = new VideoTags() + }; + flvTags.VideoTagsData.FrameType = FrameType.KeyFrame; + flvTags.VideoTagsData.VideoData = new AvcVideoPacke { - Value = width - }); - flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_Height + AvcPacketType = AvcPacketType.SequenceHeader, + CompositionTime = 0 + }; + AVCDecoderConfigurationRecord aVCDecoderConfigurationRecord = new AVCDecoderConfigurationRecord { - Value = height - }); + AVCProfileIndication = spsInfo.profileIdc, + ProfileCompatibility = (byte)spsInfo.profileCompat, + AVCLevelIndication = spsInfo.levelIdc, + NumOfPictureParameterSets = 1, + PPSBuffer = pps.RawData, + SPSBuffer = sps.RawData + }; + flvTags.VideoTagsData.VideoData.AVCDecoderConfiguration = aVCDecoderConfigurationRecord; + flvMessagePackWriter.WriteUInt32(previousTagSize); flvMessagePackWriter.WriteFlvTag(flvTags); - return flvMessagePackWriter.FlushAndGetArray(); + var data = flvMessagePackWriter.FlushAndGetArray(); + previousTagSize = (uint)(flvTags.DataSize + 11); + return data; } finally { FlvArrayPool.Return(buffer); } } - internal byte[] CreateVideoTag0Frame(byte[] spsRawData, byte[] ppsRawData, SPSInfo spsInfo) + + /// + /// 编码首帧音频,即audioTag[0] + /// + /// 注意:本方法已写入 + /// + /// + /// + public byte[] EncoderFirstAudioTag(ulong timestamp) { byte[] buffer = FlvArrayPool.Rent(2048); try { FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); - //flv body video tag + //flv body audio tag //flv body tag header - FlvTags flvTags = new FlvTags(); - flvTags.Type = TagType.Video; - flvTags.Timestamp = 0; - flvTags.TimestampExt = 0; - flvTags.StreamId = 0; - //flv body tag body - flvTags.VideoTagsData = new VideoTags(); - flvTags.VideoTagsData.FrameType = FrameType.KeyFrame; - flvTags.VideoTagsData.VideoData = new AvcVideoPacke(); - flvTags.VideoTagsData.VideoData.AvcPacketType = AvcPacketType.SequenceHeader; - flvTags.VideoTagsData.VideoData.CompositionTime = 0; - AVCDecoderConfigurationRecord aVCDecoderConfigurationRecord = new AVCDecoderConfigurationRecord(); - aVCDecoderConfigurationRecord.AVCProfileIndication = spsInfo.profileIdc; - aVCDecoderConfigurationRecord.ProfileCompatibility = (byte)spsInfo.profileCompat; - aVCDecoderConfigurationRecord.AVCLevelIndication = spsInfo.levelIdc; - aVCDecoderConfigurationRecord.NumOfPictureParameterSets = 1; - aVCDecoderConfigurationRecord.PPSBuffer = ppsRawData; - aVCDecoderConfigurationRecord.SPSBuffer = spsRawData; - flvTags.VideoTagsData.VideoData.AVCDecoderConfiguration = aVCDecoderConfigurationRecord; + FlvTags flvTags = new FlvTags + { + Type = TagType.Audio, + Timestamp = (uint)timestamp, + //flv body tag body + AudioTagsData = new AudioTags(AACPacketType.AudioSpecificConfig) + }; + flvMessagePackWriter.WriteUInt32(previousTagSize); flvMessagePackWriter.WriteFlvTag(flvTags); - return flvMessagePackWriter.FlushAndGetArray(); + var data = flvMessagePackWriter.FlushAndGetArray(); + previousTagSize = (uint)(flvTags.DataSize + 11); + return data; } finally { FlvArrayPool.Return(buffer); } } - internal byte[] CreateVideoTagOtherFrame(FlvFrameInfo flvFrameInfo, H264NALU nALU, H264NALU sei) + + /// + /// 编码非首帧视频 + /// + /// + /// 是否需要首帧视频 + /// + public byte[] EncoderVideoTag(JT1078Package package, bool needVideoHeader = false) + { + if (package.Label3.DataType == JT1078DataType.音频帧) return default; + byte[] buffer = FlvArrayPool.Rent(65535); + FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(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); + if (needVideoHeader) + { + if (needVideoHeader) + { + var firstVideoTag = EncoderFirstVideoTag(sps, pps, sei); + flvMessagePackWriter.WriteArray(firstVideoTag); + } + } + foreach (var naln in nalus) + { + flvMessagePackWriter.WriteUInt32(previousTagSize); + var videoTag = ConversionNaluToVideoTag(naln); + flvMessagePackWriter.WriteArray(videoTag); + } + } + return flvMessagePackWriter.FlushAndGetArray(); + } + + /// + /// 编码非首帧音频 + /// + /// + /// 是否需要首帧音频 + /// + public byte[] EncoderAudioTag(JT1078Package package, bool needAacHeader = false) + { + if (package.Label3.DataType != JT1078DataType.音频帧) throw new Exception("Incorrect parameter, package must be audio frame"); + FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(new byte[65536]); + if (needAacHeader) + { + flvMessagePackWriter.WriteArray(EncoderFirstAudioTag(package.Timestamp)); + } + byte[] aacFrameData = null; + switch (package.Label2.PT) + { + case Jt1078AudioType.ADPCM: + ReadOnlySpan adpcm = package.Bodies; + // 海思芯片编码的音频需要移除海思头,可能还有其他的海思头 + if (adpcm.StartsWith(new byte[] { 0x00, 0x01, 0x52, 0x00 })) adpcm = adpcm.Slice(4); + aacFrameData = faacEncoder.Encode(new AdpcmCodec().ToPcm(adpcm.Slice(4).ToArray(), new State() + { + Valprev = (short)((adpcm[1] << 8) | adpcm[0]), + Index = adpcm[2], + Reserved = adpcm[3] + })); break; + case Jt1078AudioType.G711A: + aacFrameData = faacEncoder.Encode(new G711ACodec().ToPcm(package.Bodies)); + break; + case Jt1078AudioType.AACLC: + aacFrameData = package.Bodies; + break; + } + if (aacFrameData != null && aacFrameData.Any())//编码成功,此时为一帧aac音频数据 + { + // PreviousTagSize + flvMessagePackWriter.WriteUInt32(previousTagSize); + // Data Tag Frame + flvMessagePackWriter.WriteArray(ConversionAacDataToAudioTag((uint)package.Timestamp, aacFrameData)); + } + return flvMessagePackWriter.FlushAndGetArray(); + } + + byte[] ConversionNaluToVideoTag(H264NALU nALU) { byte[] buffer = FlvArrayPool.Rent(65535); try @@ -343,16 +299,20 @@ namespace JT1078.Flv FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); //flv body video tag //flv body tag header - FlvTags flvTags = new FlvTags(); - flvTags.Type = TagType.Video; - //pts - flvTags.Timestamp = flvFrameInfo.Interval; - flvTags.TimestampExt = 0; - flvTags.StreamId = 0; - //flv body tag body - flvTags.VideoTagsData = new VideoTags(); - flvTags.VideoTagsData.VideoData = new AvcVideoPacke(); - flvTags.VideoTagsData.VideoData.AvcPacketType = AvcPacketType.Raw; + FlvTags flvTags = new FlvTags + { + Type = TagType.Video, + //pts + Timestamp = (uint)nALU.Timestamp, + TimestampExt = 0, + StreamId = 0, + //flv body tag body + VideoTagsData = new VideoTags() + }; + flvTags.VideoTagsData.VideoData = new AvcVideoPacke + { + AvcPacketType = AvcPacketType.Raw + }; //1: keyframe (for AVC, a seekable frame) —— 即H.264的IDR帧; //2: inter frame(for AVC, a non - seekable frame) —— H.264的普通I帧; //ref:https://www.cnblogs.com/chyingp/p/flv-getting-started.html @@ -364,16 +324,7 @@ namespace JT1078.Flv { flvTags.VideoTagsData.FrameType = FrameType.InterFrame; } - if (flvFrameInfo.LastDataType == JT1078DataType.视频I帧) - { - //cts - flvTags.VideoTagsData.VideoData.CompositionTime = nALU.LastIFrameInterval; - } - else - { - //cts - flvTags.VideoTagsData.VideoData.CompositionTime = nALU.LastFrameInterval; - } + flvTags.VideoTagsData.VideoData.CompositionTime = nALU.LastFrameInterval; flvTags.VideoTagsData.VideoData.MultiData = new List(); flvTags.VideoTagsData.VideoData.MultiData.Add(nALU.RawData); //忽略sei @@ -382,69 +333,46 @@ namespace JT1078.Flv // flvTags.VideoTagsData.VideoData.MultiData.Add(sei.RawData); //} flvMessagePackWriter.WriteFlvTag(flvTags); - return flvMessagePackWriter.FlushAndGetArray(); + var data = flvMessagePackWriter.FlushAndGetArray(); + previousTagSize = (uint)(flvTags.DataSize + 11); + return data; } finally { FlvArrayPool.Return(buffer); } } - internal (byte[] Buffer, uint PreviousTagSize) CreateFirstFlvKeyFrame(byte[] spsRawData, byte[] ppsRawData, SPSInfo spsInfo, uint previousTagSize = 0) + + byte[] ConversionAacDataToAudioTag(uint timestamp, byte[] aacFrameData) { byte[] buffer = FlvArrayPool.Rent(65535); try { FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); - //flv body PreviousTagSize awalys 0 - flvMessagePackWriter.WriteUInt32(previousTagSize); - //flv body script tag - var scriptTagFrameBuffer = CreateScriptTagFrame(spsInfo.width, spsInfo.height); - flvMessagePackWriter.WriteArray(scriptTagFrameBuffer); - //flv script tag PreviousTagSize - flvMessagePackWriter.WriteUInt32((uint)scriptTagFrameBuffer.Length); - //flv body video tag 0 - var videoTagFrame0Buffer = CreateVideoTag0Frame(spsRawData, ppsRawData, spsInfo); - flvMessagePackWriter.WriteArray(videoTagFrame0Buffer); - uint videoTag0PreviousTagSize = (uint)videoTagFrame0Buffer.Length; - return (flvMessagePackWriter.FlushAndGetArray(), videoTag0PreviousTagSize); + //flv body audio tag + //flv body tag header + FlvTags flvTags = new FlvTags + { + Type = TagType.Audio, + Timestamp = timestamp, + TimestampExt = 0, + StreamId = 0, + //flv body tag body + AudioTagsData = new AudioTags(AACPacketType.AudioFrame, aacFrameData) + }; + flvMessagePackWriter.WriteFlvTag(flvTags); + previousTagSize = (uint)(flvTags.DataSize + 11); + return flvMessagePackWriter.FlushAndGetArray(); } finally { FlvArrayPool.Return(buffer); } } - } - - /// - /// flv存储帧信息 - /// - internal class FlvFrameInfo - { - /// - /// flv上一帧的数据大小 - /// - public uint PreviousTagSize { get; set; } - /// - /// 1078当前时间戳 - /// - public ulong Timestamp { get; set; } - - /// - /// 1078当前音频时间戳 - /// - public ulong AudioTimestamp { get; set; } - /// - /// 与flv上一帧相减的时间间隔 - /// - public uint Interval { get; set; } - /// - /// 上一帧音频的时间间隔 - /// - public uint AudioInterval { get; set; } - /// - /// 1078数据类型 - /// - public JT1078DataType LastDataType { get; set; } + public void Dispose() + { + faacEncoder.Dispose(); + } } } diff --git a/src/JT1078.Flv/JT1078.Flv.xml b/src/JT1078.Flv/JT1078.Flv.xml index ad37679..b90db51 100644 --- a/src/JT1078.Flv/JT1078.Flv.xml +++ b/src/JT1078.Flv/JT1078.Flv.xml @@ -205,71 +205,88 @@ - - - Flv固定头部数据 - - - + + Flv编码器 + 一个客户端对应一个实例 + + 当实例不适用时,尽量手动调用下 + - - - 由于获取的SIM卡可能为000000000000,所以如果有替换JT1078Package.GetKey()的值 - + 手动编码 + 1、 + 2、 + 3、 + 4、 + 5、第二个参数传false + 6、第二个参数传false + 自动编码 + 1、 + 2、 + 3、第二个参数传true + 4、第二个参数传true + + + + + 编码flv头 + + 注意:本方法已写入 + + + + - + - + 编码脚本Tag + + 注意:本方法已写入 + - 完整的1078包 - 由于获取的SIM卡可能为000000000000,所以如果有替换JT1078Package.GetKey()的值 - 默认65535 + 视频宽度 + 视频高度 + 是否含有音频,如果有,则写入音频配置,后来发现即便是有音频,这里给false也没关系 + 帧率 - + - + 编码首帧视频,即videoTag[0] + + 注意:本方法已写入 + - 设备号+通道号(1111111_1) - 当前接收到的flv数据 + + + - + - flv存储帧信息 - - - - - flv上一帧的数据大小 - - - - - 1078当前时间戳 - - - - - 1078当前音频时间戳 - - - - - 与flv上一帧相减的时间间隔 + 编码首帧音频,即audioTag[0] + + 注意:本方法已写入 + + - + - 上一帧音频的时间间隔 + 编码非首帧视频 + + 是否需要首帧视频 + - + - 1078数据类型 + 编码非首帧音频 + + 是否需要首帧音频 + diff --git a/src/JT1078.Flv/Metadata/Amf3Metadata_AudioCodecId.cs b/src/JT1078.Flv/Metadata/Amf3Metadata_AudioCodecId.cs new file mode 100644 index 0000000..9cb023c --- /dev/null +++ b/src/JT1078.Flv/Metadata/Amf3Metadata_AudioCodecId.cs @@ -0,0 +1,27 @@ +using JT1078.Flv.Extensions; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Text; + +namespace JT1078.Flv.Metadata +{ + public class Amf3Metadata_AudioCodecId : IAmf3Metadata + { + public ushort FieldNameLength { get; set; } + public string FieldName { get; set; } = "audiocodecid"; + public byte DataType { get; set; } = 0x00; + public object Value { get; set; } = 10d; + + public ReadOnlySpan ToBuffer() + { + var b1 = Encoding.ASCII.GetBytes(FieldName); + Span tmp = new byte[2 + b1.Length + 1 + 8]; + BinaryPrimitives.WriteUInt16BigEndian(tmp, (ushort)b1.Length); + b1.CopyTo(tmp.Slice(2)); + tmp[FieldName.Length + 2] = DataType; + this.WriteDouble(tmp.Slice(b1.Length + 3)); + return tmp; + } + } +} diff --git a/src/JT1078.Flv/Metadata/Amf3Metadata_AudioSampleRate.cs b/src/JT1078.Flv/Metadata/Amf3Metadata_AudioSampleRate.cs new file mode 100644 index 0000000..524a92a --- /dev/null +++ b/src/JT1078.Flv/Metadata/Amf3Metadata_AudioSampleRate.cs @@ -0,0 +1,27 @@ +using JT1078.Flv.Extensions; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Text; + +namespace JT1078.Flv.Metadata +{ + public class Amf3Metadata_AudioSampleRate : IAmf3Metadata + { + public ushort FieldNameLength { get; set; } + public string FieldName { get; set; } = "audiosamplerate"; + public byte DataType { get; set; } = 0x00; + public object Value { get; set; } = 8000d; + + public ReadOnlySpan ToBuffer() + { + var b1 = Encoding.ASCII.GetBytes(FieldName); + Span tmp = new byte[2 + b1.Length + 1 + 8]; + BinaryPrimitives.WriteUInt16BigEndian(tmp, (ushort)b1.Length); + b1.CopyTo(tmp.Slice(2)); + tmp[FieldName.Length + 2] = DataType; + this.WriteDouble(tmp.Slice(b1.Length + 3)); + return tmp; + } + } +} diff --git a/src/JT1078.Flv/Metadata/Amf3Metadata_AudioSampleSize.cs b/src/JT1078.Flv/Metadata/Amf3Metadata_AudioSampleSize.cs new file mode 100644 index 0000000..b90cc72 --- /dev/null +++ b/src/JT1078.Flv/Metadata/Amf3Metadata_AudioSampleSize.cs @@ -0,0 +1,27 @@ +using JT1078.Flv.Extensions; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Text; + +namespace JT1078.Flv.Metadata +{ + public class Amf3Metadata_AudioSampleSize : IAmf3Metadata + { + public ushort FieldNameLength { get; set; } + public string FieldName { get; set; } = "audiosamplesize"; + public byte DataType { get; set; } = 0x00; + public object Value { get; set; } = 16d; + + public ReadOnlySpan ToBuffer() + { + var b1 = Encoding.ASCII.GetBytes(FieldName); + Span tmp = new byte[2 + b1.Length + 1 + 8]; + BinaryPrimitives.WriteUInt16BigEndian(tmp, (ushort)b1.Length); + b1.CopyTo(tmp.Slice(2)); + tmp[FieldName.Length + 2] = DataType; + this.WriteDouble(tmp.Slice(b1.Length + 3)); + return tmp; + } + } +} diff --git a/src/JT1078.Flv/Metadata/Amf3Metadata_AudioStereo.cs b/src/JT1078.Flv/Metadata/Amf3Metadata_AudioStereo.cs new file mode 100644 index 0000000..f14e80d --- /dev/null +++ b/src/JT1078.Flv/Metadata/Amf3Metadata_AudioStereo.cs @@ -0,0 +1,27 @@ +using JT1078.Flv.Extensions; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Text; + +namespace JT1078.Flv.Metadata +{ + public class Amf3Metadata_AudioStereo : IAmf3Metadata + { + public ushort FieldNameLength { get; set; } + public string FieldName { get; set; } = "stereo"; + public byte DataType { get; set; } = 0x01; + public object Value { get; set; } = false; + + public ReadOnlySpan ToBuffer() + { + var b1 = Encoding.ASCII.GetBytes(FieldName); + Span tmp = new byte[2 + b1.Length + 1 + 1]; + BinaryPrimitives.WriteUInt16BigEndian(tmp, (ushort)b1.Length); + b1.CopyTo(tmp.Slice(2)); + tmp[FieldName.Length + 2] = DataType; + this.WriteBool(tmp.Slice(b1.Length + 3)); + return tmp; + } + } +} diff --git a/src/JT1078.Protocol/Enums/Jt1078AudioType.cs b/src/JT1078.Protocol/Enums/Jt1078AudioType.cs new file mode 100644 index 0000000..ef77652 --- /dev/null +++ b/src/JT1078.Protocol/Enums/Jt1078AudioType.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace JT1078.Protocol.Enums +{ + /// + /// 音频类型 + /// + public enum Jt1078AudioType : byte + { + G721 = 1, + G722 = 2, + G723 = 3, + G728 = 4, + G729 = 5, + G711A = 6, + G711U = 7, + G726 = 8, + G729A = 9, + DVI4_3 = 10, + DVI_4 = 11, + DVI4_8K = 12, + DVI4_16K = 13, + LPC = 14, + S16BE_STEREO = 15, + S16E_MONO = 16, + MPEGAUDIO = 17, + LPCM = 18, + AAC = 19, + WMA9STD = 20, + HEAAC = 21, + PCM_VOICE = 22, + PCM_AUDIO = 23, + AACLC = 24, + MP3 = 25, + ADPCM = 26, + MP4AUDIO = 27, + AMR = 28 + } +} diff --git a/src/JT1078.Protocol/JT1078Label2.cs b/src/JT1078.Protocol/JT1078Label2.cs index bb6ad0b..c198225 100644 --- a/src/JT1078.Protocol/JT1078Label2.cs +++ b/src/JT1078.Protocol/JT1078Label2.cs @@ -1,4 +1,5 @@ -using System; +using JT1078.Protocol.Enums; +using System; using System.Text; namespace JT1078.Protocol @@ -12,7 +13,7 @@ namespace JT1078.Protocol public JT1078Label2(byte value) { M = (byte)(value >> 7); - PT = (byte)(value & 0x7f); + PT = (Jt1078AudioType)(value & 0x7f); } /// @@ -20,12 +21,23 @@ namespace JT1078.Protocol /// /// 0-1 /// 0-127 - public JT1078Label2(byte m,byte pt) + public JT1078Label2(byte m, Jt1078AudioType pt) { M = m; PT = pt; } + /// + /// + /// + /// 0-1 + /// 0-127 + public JT1078Label2(byte m,byte pt) + { + M = m; + PT = (Jt1078AudioType)pt; + } + /// /// M - 1 - 标志位,确定是否是完整数据帧的边界 /// @@ -34,11 +46,11 @@ namespace JT1078.Protocol /// PT - 7 - 负载类型 /// 用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等 /// - public byte PT { get; set; } + public Jt1078AudioType PT { get; set; } public byte ToByte() { - return (byte)((M << 7) | PT); + return (byte)((M << 7) | (byte)PT); } public string BinaryCode { get { return ToString(); } }