@@ -63,15 +63,6 @@ namespace JT1078.Flv.Benchmark | |||||
var nalus = h264Decoder.ParseNALU(Package); | 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 | public class JT1078FlvEncoderConfig : ManualConfig | ||||
@@ -37,289 +37,289 @@ namespace JT1078.Flv.Test | |||||
Assert.Equal(4, nalus.Count); | Assert.Equal(4, nalus.Count); | ||||
FlvEncoder encoder = new FlvEncoder(); | FlvEncoder encoder = new FlvEncoder(); | ||||
var contents = encoder.CreateFlvFrame(nalus); | |||||
var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_1.flv"); | var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_1.flv"); | ||||
if (File.Exists(filepath)) | if (File.Exists(filepath)) | ||||
{ | { | ||||
File.Delete(filepath); | File.Delete(filepath); | ||||
} | } | ||||
FileStream fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | 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(); | 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; | private readonly int maxOutput; | ||||
public readonly int frameSize; | public readonly int frameSize; | ||||
private List<byte> frameCache = new List<byte>(); | 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 inputSampleBytes = new byte[4]; | ||||
var maxOutputBytes = new byte[4]; | var maxOutputBytes = new byte[4]; | ||||
@@ -27,7 +27,7 @@ namespace JT1078.Flv.Audio | |||||
var ptr = FaacEncGetCurrentConfiguration(faacEncHandle); | var ptr = FaacEncGetCurrentConfiguration(faacEncHandle); | ||||
var configuration = InteropExtensions.IntPtrToStruct<FaacEncConfiguration>(ptr); | var configuration = InteropExtensions.IntPtrToStruct<FaacEncConfiguration>(ptr); | ||||
configuration.inputFormat = 1; | configuration.inputFormat = 1; | ||||
configuration.outputFormat = 0; | |||||
configuration.outputFormat = adts ? 1 : 0; | |||||
configuration.useTns = 0; | configuration.useTns = 0; | ||||
configuration.useLfe = 0; | configuration.useLfe = 0; | ||||
configuration.aacObjectType = 2; | configuration.aacObjectType = 2; | ||||
@@ -97,7 +97,7 @@ namespace JT1078.Flv.Audio | |||||
[DllImport(DLLFile, EntryPoint = "faacEncEncode", CallingConvention = CallingConvention.StdCall)] | [DllImport(DLLFile, EntryPoint = "faacEncEncode", CallingConvention = CallingConvention.StdCall)] | ||||
private extern static int FaacEncEncode(IntPtr hEncoder, byte[] inputBuffer, int samplesInput, byte[] outputBuffer, int bufferSize); | 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); | //int FAACAPI faacEncClose(faacEncHandle hEncoder); | ||||
private extern static IntPtr FaacEncClose(IntPtr hEncoder); | private extern static IntPtr FaacEncClose(IntPtr hEncoder); | ||||
@@ -15,5 +15,10 @@ namespace JT1078.Flv.Extensions | |||||
flvBuffer.Reverse(); | flvBuffer.Reverse(); | ||||
flvBuffer.CopyTo(value); | 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.Enums; | ||||
using JT1078.Flv.Extensions; | |||||
using JT1078.Flv.MessagePack; | using JT1078.Flv.MessagePack; | ||||
using JT1078.Flv.Metadata; | using JT1078.Flv.Metadata; | ||||
using JT1078.Protocol; | |||||
using JT1078.Protocol.Enums; | using JT1078.Protocol.Enums; | ||||
using JT1078.Protocol.H264; | using JT1078.Protocol.H264; | ||||
using JT1078.Protocol.MessagePack; | using JT1078.Protocol.MessagePack; | ||||
using Microsoft.Extensions.Logging; | |||||
using JT1078.Flv.Audio; | |||||
using System; | using System; | ||||
using System.Buffers.Binary; | |||||
using System.Collections.Concurrent; | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Runtime.CompilerServices; | using System.Runtime.CompilerServices; | ||||
using System.Text; | |||||
using JT1078.Protocol; | |||||
[assembly: InternalsVisibleTo("JT1078.Flv.Test")] | [assembly: InternalsVisibleTo("JT1078.Flv.Test")] | ||||
namespace JT1078.Flv | 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> | /// <summary> | ||||
/// | |||||
/// 编码脚本Tag | |||||
/// <para> | |||||
/// 注意:本方法已写入<see cref="previousTagSize"/> | |||||
/// </para> | |||||
/// </summary> | /// </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> | /// <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 | try | ||||
{ | { | ||||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | 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 | finally | ||||
{ | { | ||||
@@ -197,145 +118,180 @@ namespace JT1078.Flv | |||||
} | } | ||||
/// <summary> | /// <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> | /// </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> | /// <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 | try | ||||
{ | { | ||||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | 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 | //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); | flvMessagePackWriter.WriteFlvTag(flvTags); | ||||
return flvMessagePackWriter.FlushAndGetArray(); | |||||
var data = flvMessagePackWriter.FlushAndGetArray(); | |||||
previousTagSize = (uint)(flvTags.DataSize + 11); | |||||
return data; | |||||
} | } | ||||
finally | finally | ||||
{ | { | ||||
FlvArrayPool.Return(buffer); | 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); | byte[] buffer = FlvArrayPool.Rent(2048); | ||||
try | try | ||||
{ | { | ||||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | ||||
//flv body video tag | |||||
//flv body audio tag | |||||
//flv body tag header | //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); | flvMessagePackWriter.WriteFlvTag(flvTags); | ||||
return flvMessagePackWriter.FlushAndGetArray(); | |||||
var data = flvMessagePackWriter.FlushAndGetArray(); | |||||
previousTagSize = (uint)(flvTags.DataSize + 11); | |||||
return data; | |||||
} | } | ||||
finally | finally | ||||
{ | { | ||||
FlvArrayPool.Return(buffer); | 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); | byte[] buffer = FlvArrayPool.Rent(65535); | ||||
try | try | ||||
@@ -343,16 +299,20 @@ namespace JT1078.Flv | |||||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | ||||
//flv body video tag | //flv body video tag | ||||
//flv body tag header | //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帧; | //1: keyframe (for AVC, a seekable frame) —— 即H.264的IDR帧; | ||||
//2: inter frame(for AVC, a non - seekable frame) —— H.264的普通I帧; | //2: inter frame(for AVC, a non - seekable frame) —— H.264的普通I帧; | ||||
//ref:https://www.cnblogs.com/chyingp/p/flv-getting-started.html | //ref:https://www.cnblogs.com/chyingp/p/flv-getting-started.html | ||||
@@ -364,16 +324,7 @@ namespace JT1078.Flv | |||||
{ | { | ||||
flvTags.VideoTagsData.FrameType = FrameType.InterFrame; | 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 = new List<byte[]>(); | ||||
flvTags.VideoTagsData.VideoData.MultiData.Add(nALU.RawData); | flvTags.VideoTagsData.VideoData.MultiData.Add(nALU.RawData); | ||||
//忽略sei | //忽略sei | ||||
@@ -382,69 +333,46 @@ namespace JT1078.Flv | |||||
// flvTags.VideoTagsData.VideoData.MultiData.Add(sei.RawData); | // flvTags.VideoTagsData.VideoData.MultiData.Add(sei.RawData); | ||||
//} | //} | ||||
flvMessagePackWriter.WriteFlvTag(flvTags); | flvMessagePackWriter.WriteFlvTag(flvTags); | ||||
return flvMessagePackWriter.FlushAndGetArray(); | |||||
var data = flvMessagePackWriter.FlushAndGetArray(); | |||||
previousTagSize = (uint)(flvTags.DataSize + 11); | |||||
return data; | |||||
} | } | ||||
finally | finally | ||||
{ | { | ||||
FlvArrayPool.Return(buffer); | 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); | byte[] buffer = FlvArrayPool.Rent(65535); | ||||
try | try | ||||
{ | { | ||||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | 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 | finally | ||||
{ | { | ||||
FlvArrayPool.Return(buffer); | 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> | <param name="separator"></param> | ||||
<returns></returns> | <returns></returns> | ||||
</member> | </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> | <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> | <returns></returns> | ||||
</member> | </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> | <summary> | ||||
编码脚本Tag | |||||
<para> | |||||
注意:本方法已写入<see cref="F:JT1078.Flv.FlvEncoder.previousTagSize"/> | |||||
</para> | |||||
</summary> | </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> | <returns></returns> | ||||
</member> | </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> | <summary> | ||||
编码首帧视频,即videoTag[0] | |||||
<para> | |||||
注意:本方法已写入<see cref="F:JT1078.Flv.FlvEncoder.previousTagSize"/> | |||||
</para> | |||||
</summary> | </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> | <returns></returns> | ||||
</member> | </member> | ||||
<member name="T:JT1078.Flv.FlvFrameInfo"> | |||||
<member name="M:JT1078.Flv.FlvEncoder.DecoderFirstAudioTag(System.UInt64)"> | |||||
<summary> | <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> | </summary> | ||||
<returns></returns> | |||||
</member> | </member> | ||||
<member name="P:JT1078.Flv.FlvFrameInfo.AudioInterval"> | |||||
<member name="M:JT1078.Flv.FlvEncoder.DecoderVideoTag(JT1078.Protocol.JT1078Package,System.Boolean)"> | |||||
<summary> | <summary> | ||||
上一帧音频的时间间隔 | |||||
编码非首帧视频 | |||||
</summary> | </summary> | ||||
<param name="package"></param> | |||||
<param name="needVideoHeader">是否需要首帧视频</param> | |||||
<returns></returns> | |||||
</member> | </member> | ||||
<member name="P:JT1078.Flv.FlvFrameInfo.LastDataType"> | |||||
<member name="M:JT1078.Flv.FlvEncoder.DecoderAudioTag(JT1078.Protocol.JT1078Package,System.Boolean)"> | |||||
<summary> | <summary> | ||||
1078数据类型 | |||||
编码非首帧音频 | |||||
</summary> | </summary> | ||||
<param name="package"></param> | |||||
<param name="needAacHeader">是否需要首帧音频</param> | |||||
<returns></returns> | |||||
</member> | </member> | ||||
<member name="P:JT1078.Flv.FlvTags.DataSize"> | <member name="P:JT1078.Flv.FlvTags.DataSize"> | ||||
<summary> | <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 | public class JT1078FlvEncoderConfig : ManualConfig | ||||
@@ -274,7 +274,7 @@ namespace JT1078.Protocol.Test | |||||
JT1078Label2 label2 = new JT1078Label2(254); | JT1078Label2 label2 = new JT1078Label2(254); | ||||
Assert.Equal(254, label2.ToByte()); | Assert.Equal(254, label2.ToByte()); | ||||
Assert.Equal(1, label2.M); | Assert.Equal(1, label2.M); | ||||
Assert.Equal(126, label2.PT); | |||||
Assert.Equal(126, (byte)label2.PT); | |||||
} | } | ||||
[Fact] | [Fact] | ||||
@@ -283,7 +283,7 @@ namespace JT1078.Protocol.Test | |||||
JT1078Label2 label2 = new JT1078Label2(0, 28); | JT1078Label2 label2 = new JT1078Label2(0, 28); | ||||
Assert.Equal(28, label2.ToByte()); | Assert.Equal(28, label2.ToByte()); | ||||
Assert.Equal(0, label2.M); | Assert.Equal(0, label2.M); | ||||
Assert.Equal(28, label2.PT); | |||||
Assert.Equal(Jt1078AudioType.AMR, label2.PT); | |||||
} | } | ||||
[Fact] | [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; | using System.Text; | ||||
namespace JT1078.Protocol | namespace JT1078.Protocol | ||||
@@ -12,7 +13,7 @@ namespace JT1078.Protocol | |||||
public JT1078Label2(byte value) | public JT1078Label2(byte value) | ||||
{ | { | ||||
M = (byte)(value >> 7); | M = (byte)(value >> 7); | ||||
PT = (byte)(value & 0x7f); | |||||
PT = (Jt1078AudioType)(value & 0x7f); | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
@@ -20,12 +21,23 @@ namespace JT1078.Protocol | |||||
/// </summary> | /// </summary> | ||||
/// <param name="m">0-1</param> | /// <param name="m">0-1</param> | ||||
/// <param name="pt">0-127</param> | /// <param name="pt">0-127</param> | ||||
public JT1078Label2(byte m,byte pt) | |||||
public JT1078Label2(byte m, Jt1078AudioType pt) | |||||
{ | { | ||||
M = m; | M = m; | ||||
PT = pt; | 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> | /// <summary> | ||||
/// M - 1 - 标志位,确定是否是完整数据帧的边界 | /// M - 1 - 标志位,确定是否是完整数据帧的边界 | ||||
/// </summary> | /// </summary> | ||||
@@ -34,11 +46,11 @@ namespace JT1078.Protocol | |||||
/// PT - 7 - 负载类型 | /// PT - 7 - 负载类型 | ||||
/// 用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等 | /// 用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等 | ||||
/// </summary> | /// </summary> | ||||
public byte PT { get; set; } | |||||
public Jt1078AudioType PT { get; set; } | |||||
public byte ToByte() | public byte ToByte() | ||||
{ | { | ||||
return (byte)((M << 7) | PT); | |||||
return (byte)((M << 7) | (byte)PT); | |||||
} | } | ||||
public string BinaryCode { get { return ToString(); } } | public string BinaryCode { get { return ToString(); } } | ||||