@@ -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 | |||
@@ -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<H264NALU> h264NALULs = new List<H264NALU>(); | |||
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<H264NALU> h264NALULs = new List<H264NALU>(); | |||
// 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<H264NALU> h264NALULs = new List<H264NALU>(); | |||
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<H264NALU> h264NALULs = new List<H264NALU>(); | |||
// 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<Exception>(()=> { }); | |||
} | |||
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<Exception>(() => { }); | |||
// } | |||
// finally | |||
// { | |||
// fileStream?.Close(); | |||
// fileStream?.Dispose(); | |||
// } | |||
//} | |||
[Fact] | |||
public void 测试可以播放的Flv2() | |||
{ | |||
FileStream fileStream = null; | |||
H264Decoder decoder = new H264Decoder(); | |||
List<H264NALU> h264NALULs = new List<H264NALU>(); | |||
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<H264NALU> h264NALULs = new List<H264NALU>(); | |||
// 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<Exception>(() => { }); | |||
} | |||
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<Exception>(() => { }); | |||
// } | |||
// finally | |||
// { | |||
// fileStream?.Close(); | |||
// fileStream?.Dispose(); | |||
// } | |||
//} | |||
[Fact] | |||
public void 测试主次码流切换() | |||
{ | |||
FileStream fileStream = null; | |||
H264Decoder decoder = new H264Decoder(); | |||
List<H264NALU> h264NALULs = new List<H264NALU>(); | |||
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<H264NALU> h264NALULs = new List<H264NALU>(); | |||
// 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<SPSInfo> tmpSpss = new List<SPSInfo>(); | |||
List<ulong> times = new List<ulong>(); | |||
List<ushort> lastIFrameIntervals = new List<ushort>(); | |||
List<ushort> lastFrameIntervals = new List<ushort>(); | |||
List<int> type = new List<int>(); | |||
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<Exception>(() => { }); | |||
} | |||
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<SPSInfo> tmpSpss = new List<SPSInfo>(); | |||
// List<ulong> times = new List<ulong>(); | |||
// List<ushort> lastIFrameIntervals = new List<ushort>(); | |||
// List<ushort> lastFrameIntervals = new List<ushort>(); | |||
// List<int> type = new List<int>(); | |||
// 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<Exception>(() => { }); | |||
// } | |||
// 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); | |||
//} | |||
} | |||
} |
@@ -14,7 +14,7 @@ namespace JT1078.Flv.Audio | |||
private readonly int maxOutput; | |||
public readonly int frameSize; | |||
private List<byte> frameCache = new List<byte>(); | |||
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<FaacEncConfiguration>(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); | |||
@@ -15,5 +15,10 @@ namespace JT1078.Flv.Extensions | |||
flvBuffer.Reverse(); | |||
flvBuffer.CopyTo(value); | |||
} | |||
public static void WriteBool(this IAmf3Metadata metadata, Span<byte> value) | |||
{ | |||
var flvBuffer = BitConverter.GetBytes(Convert.ToBoolean(metadata.Value)).AsSpan(); | |||
flvBuffer.CopyTo(value); | |||
} | |||
} | |||
} |
@@ -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 | |||
/// <summary> | |||
/// Flv编码器 | |||
/// 一个客户端对应一个实例 | |||
/// <para> | |||
/// 当实例不适用时,尽量手动调用下<see cref="Dispose"/> | |||
/// </para> | |||
/// | |||
/// 手动编码 | |||
/// 1、<see cref="EncoderFlvHeader"/> | |||
/// 2、<see cref="EncoderScriptTag"/> | |||
/// 3、<see cref="EncoderFirstVideoTag"/> | |||
/// 4、<see cref="EncoderFirstAudioTag"/> | |||
/// 5、<see cref="EncoderVideoTag"/>第二个参数传false | |||
/// 6、<see cref="EncoderAudioTag"/>第二个参数传false | |||
/// 自动编码 | |||
/// 1、<see cref="EncoderFlvHeader"/> | |||
/// 2、<see cref="EncoderScriptTag"/> | |||
/// 3、<see cref="EncoderVideoTag"/>第二个参数传true | |||
/// 4、<see cref="EncoderAudioTag"/>第二个参数传true | |||
/// </summary> | |||
public class FlvEncoder : IDisposable | |||
{ | |||
/// <summary> | |||
/// Flv固定头部数据 | |||
/// </summary> | |||
public static readonly byte[] VideoFlvHeaderBuffer; | |||
private static readonly H264Decoder H264Decoder; | |||
private static readonly ConcurrentDictionary<string, SPSInfo> VideoSPSDict; | |||
private static readonly ConcurrentDictionary<string, FlvFrameInfo> FlvFrameInfoDict; | |||
internal static readonly ConcurrentDictionary<string, (uint PreviousTagSize, byte[] Buffer, bool Changed)> 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<string, SPSInfo>(StringComparer.OrdinalIgnoreCase); | |||
FlvFrameInfoDict = new ConcurrentDictionary<string, FlvFrameInfo>(StringComparer.OrdinalIgnoreCase); | |||
FirstFlvFrameCache = new ConcurrentDictionary<string, (uint PreviousTagSize, byte[] Buffer, bool Changed)>(StringComparer.OrdinalIgnoreCase); | |||
H264Decoder = new H264Decoder(); | |||
faacEncoder = new FaacEncoder(sampleRate, channels, sampleBit, adts); | |||
} | |||
public FlvEncoder() | |||
{ | |||
} | |||
public FlvEncoder(ILoggerFactory loggerFactory) | |||
/// <summary> | |||
/// 编码flv头 | |||
/// <para> | |||
/// 注意:本方法已写入<see cref="previousTagSize"/> | |||
/// </para> | |||
/// </summary> | |||
/// <param name="hasVideo"></param> | |||
/// <param name="hasAudio"></param> | |||
/// <returns></returns> | |||
public byte[] EncoderFlvHeader(bool hasVideo = true, bool hasAudio = false) | |||
{ | |||
logger = loggerFactory.CreateLogger("FlvEncoder"); | |||
previousTagSize = 0; | |||
flvHeader = new FlvHeader(hasVideo, hasAudio); | |||
return flvHeader.ToArray().ToArray(); | |||
} | |||
/// <summary> | |||
/// | |||
/// 编码脚本Tag | |||
/// <para> | |||
/// 注意:本方法已写入<see cref="previousTagSize"/> | |||
/// </para> | |||
/// </summary> | |||
/// <param name="nALUs"></param> | |||
/// <param name="key">由于获取的SIM卡可能为000000000000,所以如果有替换JT1078Package.GetKey()的值</param> | |||
/// <param name="minimumLength"></param> | |||
/// <param name="width">视频宽度</param> | |||
/// <param name="height">视频高度</param> | |||
/// <param name="hasAudio">是否含有音频,如果有,则写入音频配置,后来发现即便是有音频,这里给<c>false</c>也没关系</param> | |||
/// <param name="frameRate">帧率</param> | |||
/// <returns></returns> | |||
public byte[] CreateFlvFrame(List<H264NALU> 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<IAmf3Metadata> | |||
{ | |||
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 | |||
} | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
/// <param name="package">完整的1078包</param> | |||
/// <param name="key">由于获取的SIM卡可能为000000000000,所以如果有替换JT1078Package.GetKey()的值</param> | |||
/// <param name="minimumLength">默认65535</param> | |||
/// <returns></returns> | |||
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); | |||
} | |||
/// <summary> | |||
/// | |||
/// 编码首帧视频,即videoTag[0] | |||
/// <para> | |||
/// 注意:本方法已写入<see cref="previousTagSize"/> | |||
/// </para> | |||
/// </summary> | |||
/// <param name="key">设备号+通道号(1111111_1)</param> | |||
/// <param name="currentBufferFlvFrame">当前接收到的flv数据</param> | |||
/// <param name="sps"></param> | |||
/// <param name="pps"></param> | |||
/// <param name="sei"></param> | |||
/// <returns></returns> | |||
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<byte> 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<IAmf3Metadata>(); | |||
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) | |||
/// <summary> | |||
/// 编码首帧音频,即audioTag[0] | |||
/// <para> | |||
/// 注意:本方法已写入<see cref="previousTagSize"/> | |||
/// </para> | |||
/// </summary> | |||
/// <returns></returns> | |||
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) | |||
/// <summary> | |||
/// 编码非首帧视频 | |||
/// </summary> | |||
/// <param name="package"></param> | |||
/// <param name="needVideoHeader">是否需要首帧视频</param> | |||
/// <returns></returns> | |||
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(); | |||
} | |||
/// <summary> | |||
/// 编码非首帧音频 | |||
/// </summary> | |||
/// <param name="package"></param> | |||
/// <param name="needAacHeader">是否需要首帧音频</param> | |||
/// <returns></returns> | |||
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<byte> 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<byte[]>(); | |||
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); | |||
} | |||
} | |||
} | |||
/// <summary> | |||
/// flv存储帧信息 | |||
/// </summary> | |||
internal class FlvFrameInfo | |||
{ | |||
/// <summary> | |||
/// flv上一帧的数据大小 | |||
/// </summary> | |||
public uint PreviousTagSize { get; set; } | |||
/// <summary> | |||
/// 1078当前时间戳 | |||
/// </summary> | |||
public ulong Timestamp { get; set; } | |||
/// <summary> | |||
/// 1078当前音频时间戳 | |||
/// </summary> | |||
public ulong AudioTimestamp { get; set; } | |||
/// <summary> | |||
/// 与flv上一帧相减的时间间隔 | |||
/// </summary> | |||
public uint Interval { get; set; } | |||
/// <summary> | |||
/// 上一帧音频的时间间隔 | |||
/// </summary> | |||
public uint AudioInterval { get; set; } | |||
/// <summary> | |||
/// 1078数据类型 | |||
/// </summary> | |||
public JT1078DataType LastDataType { get; set; } | |||
public void Dispose() | |||
{ | |||
faacEncoder.Dispose(); | |||
} | |||
} | |||
} |
@@ -205,71 +205,88 @@ | |||
<param name="separator"></param> | |||
<returns></returns> | |||
</member> | |||
<member name="F:JT1078.Flv.FlvEncoder.VideoFlvHeaderBuffer"> | |||
<summary> | |||
Flv固定头部数据 | |||
</summary> | |||
</member> | |||
<member name="M:JT1078.Flv.FlvEncoder.CreateFlvFrame(System.Collections.Generic.List{JT1078.Protocol.H264.H264NALU},System.String,System.Int32)"> | |||
<member name="T:JT1078.Flv.FlvEncoder"> | |||
<summary> | |||
Flv编码器 | |||
一个客户端对应一个实例 | |||
<para> | |||
当实例不适用时,尽量手动调用下<see cref="M:JT1078.Flv.FlvEncoder.Dispose"/> | |||
</para> | |||
</summary> | |||
<param name="nALUs"></param> | |||
<param name="key">由于获取的SIM卡可能为000000000000,所以如果有替换JT1078Package.GetKey()的值</param> | |||
<param name="minimumLength"></param> | |||
手动编码 | |||
1、<see cref="M:JT1078.Flv.FlvEncoder.DecoderFlvHeader(System.Boolean,System.Boolean)"/> | |||
2、<see cref="M:JT1078.Flv.FlvEncoder.DecoderScriptTag(System.Boolean,System.Double)"/> | |||
3、<see cref="M:JT1078.Flv.FlvEncoder.DecoderFirstVideoTag(JT1078.Protocol.H264.H264NALU,JT1078.Protocol.H264.H264NALU,JT1078.Protocol.H264.H264NALU)"/> | |||
4、<see cref="M:JT1078.Flv.FlvEncoder.DecoderFirstAudioTag(System.UInt64)"/> | |||
5、<see cref="M:JT1078.Flv.FlvEncoder.DecoderVideoTag(JT1078.Protocol.JT1078Package,System.Boolean)"/>第二个参数传false | |||
6、<see cref="M:JT1078.Flv.FlvEncoder.DecoderAudioTag(JT1078.Protocol.JT1078Package,System.Boolean)"/>第二个参数传false | |||
自动编码 | |||
1、<see cref="M:JT1078.Flv.FlvEncoder.DecoderFlvHeader(System.Boolean,System.Boolean)"/> | |||
2、<see cref="M:JT1078.Flv.FlvEncoder.DecoderScriptTag(System.Boolean,System.Double)"/> | |||
3、<see cref="M:JT1078.Flv.FlvEncoder.DecoderVideoTag(JT1078.Protocol.JT1078Package,System.Boolean)"/>第二个参数传true | |||
4、<see cref="M:JT1078.Flv.FlvEncoder.DecoderAudioTag(JT1078.Protocol.JT1078Package,System.Boolean)"/>第二个参数传true | |||
</summary> | |||
</member> | |||
<member name="M:JT1078.Flv.FlvEncoder.DecoderFlvHeader(System.Boolean,System.Boolean)"> | |||
<summary> | |||
编码flv头 | |||
<para> | |||
注意:本方法已写入<see cref="F:JT1078.Flv.FlvEncoder.previousTagSize"/> | |||
</para> | |||
</summary> | |||
<param name="hasVideo"></param> | |||
<param name="hasAudio"></param> | |||
<returns></returns> | |||
</member> | |||
<member name="M:JT1078.Flv.FlvEncoder.CreateFlvFrame(JT1078.Protocol.JT1078Package,System.String,System.Int32)"> | |||
<member name="M:JT1078.Flv.FlvEncoder.DecoderScriptTag(System.Boolean,System.Double)"> | |||
<summary> | |||
编码脚本Tag | |||
<para> | |||
注意:本方法已写入<see cref="F:JT1078.Flv.FlvEncoder.previousTagSize"/> | |||
</para> | |||
</summary> | |||
<param name="package">完整的1078包</param> | |||
<param name="key">由于获取的SIM卡可能为000000000000,所以如果有替换JT1078Package.GetKey()的值</param> | |||
<param name="minimumLength">默认65535</param> | |||
<param name="width">视频宽度</param> | |||
<param name="height">视频高度</param> | |||
<param name="hasAudio">是否含有音频,如果有,则写入音频配置,后来发现即便是有音频,这里给<c>false</c>也没关系</param> | |||
<param name="frameRate">帧率</param> | |||
<returns></returns> | |||
</member> | |||
<member name="M:JT1078.Flv.FlvEncoder.GetFirstFlvFrame(System.String,System.Byte[])"> | |||
<member name="M:JT1078.Flv.FlvEncoder.DecoderFirstVideoTag(JT1078.Protocol.H264.H264NALU,JT1078.Protocol.H264.H264NALU,JT1078.Protocol.H264.H264NALU)"> | |||
<summary> | |||
编码首帧视频,即videoTag[0] | |||
<para> | |||
注意:本方法已写入<see cref="F:JT1078.Flv.FlvEncoder.previousTagSize"/> | |||
</para> | |||
</summary> | |||
<param name="key">设备号+通道号(1111111_1)</param> | |||
<param name="currentBufferFlvFrame">当前接收到的flv数据</param> | |||
<param name="sps"></param> | |||
<param name="pps"></param> | |||
<param name="sei"></param> | |||
<returns></returns> | |||
</member> | |||
<member name="T:JT1078.Flv.FlvFrameInfo"> | |||
<member name="M:JT1078.Flv.FlvEncoder.DecoderFirstAudioTag(System.UInt64)"> | |||
<summary> | |||
flv存储帧信息 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvFrameInfo.PreviousTagSize"> | |||
<summary> | |||
flv上一帧的数据大小 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvFrameInfo.Timestamp"> | |||
<summary> | |||
1078当前时间戳 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvFrameInfo.AudioTimestamp"> | |||
<summary> | |||
1078当前音频时间戳 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvFrameInfo.Interval"> | |||
<summary> | |||
与flv上一帧相减的时间间隔 | |||
编码首帧音频,即audioTag[0] | |||
<para> | |||
注意:本方法已写入<see cref="F:JT1078.Flv.FlvEncoder.previousTagSize"/> | |||
</para> | |||
</summary> | |||
<returns></returns> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvFrameInfo.AudioInterval"> | |||
<member name="M:JT1078.Flv.FlvEncoder.DecoderVideoTag(JT1078.Protocol.JT1078Package,System.Boolean)"> | |||
<summary> | |||
上一帧音频的时间间隔 | |||
编码非首帧视频 | |||
</summary> | |||
<param name="package"></param> | |||
<param name="needVideoHeader">是否需要首帧视频</param> | |||
<returns></returns> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvFrameInfo.LastDataType"> | |||
<member name="M:JT1078.Flv.FlvEncoder.DecoderAudioTag(JT1078.Protocol.JT1078Package,System.Boolean)"> | |||
<summary> | |||
1078数据类型 | |||
编码非首帧音频 | |||
</summary> | |||
<param name="package"></param> | |||
<param name="needAacHeader">是否需要首帧音频</param> | |||
<returns></returns> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvTags.DataSize"> | |||
<summary> | |||
@@ -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<byte> ToBuffer() | |||
{ | |||
var b1 = Encoding.ASCII.GetBytes(FieldName); | |||
Span<byte> 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; | |||
} | |||
} | |||
} |
@@ -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<byte> ToBuffer() | |||
{ | |||
var b1 = Encoding.ASCII.GetBytes(FieldName); | |||
Span<byte> 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; | |||
} | |||
} | |||
} |
@@ -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<byte> ToBuffer() | |||
{ | |||
var b1 = Encoding.ASCII.GetBytes(FieldName); | |||
Span<byte> 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; | |||
} | |||
} | |||
} |
@@ -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<byte> ToBuffer() | |||
{ | |||
var b1 = Encoding.ASCII.GetBytes(FieldName); | |||
Span<byte> 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; | |||
} | |||
} | |||
} |
@@ -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 | |||
@@ -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] | |||
@@ -0,0 +1,41 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
namespace JT1078.Protocol.Enums | |||
{ | |||
/// <summary> | |||
/// 音频类型 | |||
/// </summary> | |||
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 | |||
} | |||
} |
@@ -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); | |||
} | |||
/// <summary> | |||
@@ -20,12 +21,23 @@ namespace JT1078.Protocol | |||
/// </summary> | |||
/// <param name="m">0-1</param> | |||
/// <param name="pt">0-127</param> | |||
public JT1078Label2(byte m,byte pt) | |||
public JT1078Label2(byte m, Jt1078AudioType pt) | |||
{ | |||
M = m; | |||
PT = pt; | |||
} | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
/// <param name="m">0-1</param> | |||
/// <param name="pt">0-127</param> | |||
public JT1078Label2(byte m,byte pt) | |||
{ | |||
M = m; | |||
PT = (Jt1078AudioType)pt; | |||
} | |||
/// <summary> | |||
/// M - 1 - 标志位,确定是否是完整数据帧的边界 | |||
/// </summary> | |||
@@ -34,11 +46,11 @@ namespace JT1078.Protocol | |||
/// PT - 7 - 负载类型 | |||
/// 用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等 | |||
/// </summary> | |||
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(); } } | |||