2.增加H264NALU的解析及创建 3.增加1078组包改用byte池tags/v1.1.0
@@ -7,7 +7,7 @@ namespace JT1078.Flv | |||||
{ | { | ||||
public class FlvMuxer | public class FlvMuxer | ||||
{ | { | ||||
private readonly FlvHeader VideoFlvHeader = new FlvHeader(true, false); | |||||
private static readonly FlvHeader VideoFlvHeader = new FlvHeader(true, false); | |||||
public byte[] FlvFirstFrame() | public byte[] FlvFirstFrame() | ||||
{ | { | ||||
byte[] buffer = FlvArrayPool.Rent(10240); | byte[] buffer = FlvArrayPool.Rent(10240); | ||||
@@ -17,7 +17,7 @@ namespace JT1078.Flv | |||||
//flv header | //flv header | ||||
flvMessagePackWriter.WriteArray(VideoFlvHeader.ToArray()); | flvMessagePackWriter.WriteArray(VideoFlvHeader.ToArray()); | ||||
//flv body | //flv body | ||||
//flv body PreviousTagSize | |||||
//flv body PreviousTagSize awalys 0 | |||||
flvMessagePackWriter.WriteUInt32(0); | flvMessagePackWriter.WriteUInt32(0); | ||||
//flv body tag | //flv body tag | ||||
@@ -32,5 +32,28 @@ namespace JT1078.Flv | |||||
FlvArrayPool.Return(buffer); | FlvArrayPool.Return(buffer); | ||||
} | } | ||||
} | } | ||||
public byte[] FlvOtherFrame() | |||||
{ | |||||
byte[] buffer = FlvArrayPool.Rent(10240); | |||||
try | |||||
{ | |||||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | |||||
//flv body | |||||
//flv body PreviousTagSize | |||||
//flv body tag | |||||
//flv body tag header | |||||
//flv body tag body | |||||
return flvMessagePackWriter.FlushAndGetArray(); | |||||
} | |||||
finally | |||||
{ | |||||
FlvArrayPool.Return(buffer); | |||||
} | |||||
} | |||||
} | } | ||||
} | } |
@@ -11,8 +11,127 @@ namespace JT1078.Flv.H264 | |||||
{ | { | ||||
public class H264Demuxer | public class H264Demuxer | ||||
{ | { | ||||
public const string codecstring = "avc1."; | |||||
public List<H264NALU> ParseNALU(JT1078Package package) | |||||
{ | |||||
List<H264NALU> units = new List<H264NALU>(); | |||||
int offset = 0; | |||||
(int previousOffset, int previousContentOffset) previous = (0, 0); | |||||
int len = package.Bodies.Length; | |||||
ReadOnlySpan<byte> tmpBuffer = package.Bodies; | |||||
int index = 0; | |||||
while (offset < len) | |||||
{ | |||||
if ((len - offset - 3) < 0 || (len - offset - 4) < 0) | |||||
{ | |||||
if (previous.previousOffset == 0 && previous.previousContentOffset == 0) | |||||
{ | |||||
int startCodePrefix=4; | |||||
if (tmpBuffer.Slice(0, 3).SequenceEqual(H264NALU.Start1)) | |||||
{ | |||||
startCodePrefix = 3; | |||||
} | |||||
units.Add(Create(package, tmpBuffer.Slice(offset, 1), startCodePrefix)); | |||||
units[index++].RawData = tmpBuffer.ToArray(); | |||||
} | |||||
else | |||||
{ | |||||
units[index++].RawData = tmpBuffer.Slice(previous.previousContentOffset + (previous.previousOffset - previous.previousContentOffset)).ToArray(); | |||||
} | |||||
break; | |||||
} | |||||
if (tmpBuffer.Slice(offset, 3).SequenceEqual(H264NALU.Start1)) | |||||
{ | |||||
offset += 3; | |||||
if ((offset - 3) != 0) | |||||
{ | |||||
units[index++].RawData = tmpBuffer.Slice(previous.previousContentOffset + 3, offset - previous.previousOffset - 3).ToArray(); | |||||
} | |||||
units.Add(Create(package, tmpBuffer.Slice(offset, 1), 3)); | |||||
previous = (offset, offset - 3); | |||||
} | |||||
else if (tmpBuffer.Slice(offset, 4).SequenceEqual(H264NALU.Start2)) | |||||
{ | |||||
offset += 4; | |||||
if ((offset - 4) != 0) | |||||
{ | |||||
units[index++].RawData = tmpBuffer.Slice(previous.previousContentOffset + 4, offset - previous.previousOffset - 4).ToArray(); | |||||
} | |||||
units.Add(Create(package, tmpBuffer.Slice(offset, 1), 4)); | |||||
previous = (offset, offset - 4); | |||||
} | |||||
else | |||||
{ | |||||
offset++; | |||||
} | |||||
} | |||||
return units; | |||||
} | |||||
private H264NALU Create(JT1078Package package,ReadOnlySpan<byte> naluheader, int startCodePrefix) | |||||
{ | |||||
H264NALU nALU = new H264NALU(); | |||||
nALU.SIM = package.SIM; | |||||
nALU.Label3 = package.Label3; | |||||
nALU.LogicChannelNumber = package.LogicChannelNumber; | |||||
nALU.LastFrameInterval = package.LastFrameInterval; | |||||
nALU.LastIFrameInterval = package.LastIFrameInterval; | |||||
if (startCodePrefix == 3) | |||||
{ | |||||
nALU.StartCodePrefix = H264NALU.Start1; | |||||
} | |||||
else if (startCodePrefix == 4) | |||||
{ | |||||
nALU.StartCodePrefix = H264NALU.Start2; | |||||
} | |||||
nALU.NALUHeader = new NALUHeader(naluheader); | |||||
return nALU; | |||||
} | |||||
/// <summary> | |||||
/// Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps | |||||
/// for the NALUs to the next stream component. | |||||
/// Also, preprocess caption and sequence parameter NALUs. | |||||
/// 常用Nalu_type: | |||||
/// 0x67 (0 11 00111) SPS 非常重要 type = 7 | |||||
/// 0x68 (0 11 01000) PPS 非常重要 type = 8 | |||||
/// 0x65 (0 11 00101) IDR帧 关键帧(非常重要) type = 5 | |||||
/// 0x61 (0 11 00001) I帧 重要 type = 1 非IDR的I帧不大常见 | |||||
/// 0x41 (0 10 00001) P帧 重要 type = 1 | |||||
/// 0x01 (0 00 00001) B帧 不重要 type = 1 | |||||
/// 0x06 (0 00 00110) SEI 不重要 type = 6 | |||||
/// <see cref="https://blog.csdn.net/huibailingyu/article/details/42879573"/> | |||||
/// </summary> | |||||
/// <param name="h264NALU"></param> | |||||
/// <returns></returns> | |||||
public void NALUTypeFilter(H264NALU h264NALU) | |||||
{ | |||||
switch (h264NALU.NALUHeader.NalUnitType) | |||||
{ | |||||
//IDR | |||||
case 5: | |||||
break; | |||||
case 6: | |||||
h264NALU.RawData = DiscardEmulationPreventionBytes(h264NALU.RawData); | |||||
break; | |||||
//SPS | |||||
case 7: | |||||
h264NALU.RawData = DiscardEmulationPreventionBytes(h264NALU.RawData); | |||||
ExpGolombReader h264GolombReader = new ExpGolombReader(h264NALU.RawData); | |||||
var spsInfo = h264GolombReader.ReadSPS(); | |||||
break; | |||||
//PPS | |||||
case 8: | |||||
break; | |||||
//AUD | |||||
case 9: | |||||
break; | |||||
} | |||||
} | |||||
/// <summary> | /// <summary> | ||||
/// Expunge any "Emulation Prevention" bytes from a "Raw Byte Sequence Payload" | /// Expunge any "Emulation Prevention" bytes from a "Raw Byte Sequence Payload" | ||||
@@ -11,6 +11,46 @@ namespace JT1078.Flv.H264 | |||||
public readonly static byte[] Start2 = new byte[4] { 0, 0, 0, 1 }; | public readonly static byte[] Start2 = new byte[4] { 0, 0, 0, 1 }; | ||||
public byte[] StartCodePrefix { get; set; } | public byte[] StartCodePrefix { get; set; } | ||||
public NALUHeader NALUHeader { get; set; } | public NALUHeader NALUHeader { get; set; } | ||||
public JT1078Package JT1078Package { get; set; } | |||||
/// <summary> | |||||
/// 终端设备SIM卡号 | |||||
/// BCD[6] | |||||
/// </summary> | |||||
public string SIM { get; set; } | |||||
/// <summary> | |||||
/// 逻辑通道号 | |||||
/// </summary> | |||||
public byte LogicChannelNumber { get; set; } | |||||
/// <summary> | |||||
/// 数据类型 | |||||
/// 0000:视频I帧 | |||||
/// 0001:视频P帧 | |||||
/// 0010:视频B帧 | |||||
/// 0011:音频帧 | |||||
/// 0100:透传数据 | |||||
/// | |||||
/// 0000:原子包,不可被拆分 | |||||
/// 0001:分包处理时的第一个包 | |||||
/// 0010:分包处理是的最后一个包 | |||||
/// 0011:分包处理时间的中间包 | |||||
/// </summary> | |||||
public JT1078Label3 Label3 { get; set; } | |||||
/// <summary> | |||||
/// 该帧与上一个关键帧之间的时间间隔,单位毫秒(ms), | |||||
/// 当数据类型为非视频帧时,则没有该字段 | |||||
/// </summary> | |||||
public ushort LastIFrameInterval { get; set; } | |||||
/// <summary> | |||||
/// 该帧与上一个关键帧之间的时间间隔,单位毫秒(ms), | |||||
/// 当数据类型为非视频帧时,则没有该字段 | |||||
/// </summary> | |||||
public ushort LastFrameInterval { get; set; } | |||||
/// <summary> | |||||
/// 数据体 | |||||
/// </summary> | |||||
public byte[] RawData { get; set; } | |||||
public string GetKey() | |||||
{ | |||||
return $"{SIM}_{LogicChannelNumber.ToString()}"; | |||||
} | |||||
} | } | ||||
} | } |
@@ -63,7 +63,8 @@ namespace JT1078.Flv.MessagePack | |||||
for (int i = 0; i < scalingListCount; i++) | for (int i = 0; i < scalingListCount; i++) | ||||
{ | { | ||||
if (ReadBoolean()) | if (ReadBoolean()) | ||||
{ // seq_scaling_list_present_flag[ i ] | |||||
{ | |||||
// seq_scaling_list_present_flag[ i ] | |||||
if (i < 6) | if (i < 6) | ||||
{ | { | ||||
SkipScalingList(16); | SkipScalingList(16); | ||||
@@ -97,5 +97,20 @@ namespace JT1078.Flv.MessagePack | |||||
//Empty | //Empty | ||||
} | } | ||||
} | } | ||||
public void WriteAVCDecoderConfigurationRecord(AVCDecoderConfigurationRecord configurationRecord) | |||||
{ | |||||
WriteByte(configurationRecord.ConfigurationVersion); | |||||
WriteByte(configurationRecord.AVCProfileIndication); | |||||
WriteByte(configurationRecord.ProfileCompatibility); | |||||
WriteByte(configurationRecord.AVCLevelIndication); | |||||
WriteByte((byte)configurationRecord.LengthSizeMinusOne); | |||||
WriteByte((byte)configurationRecord.NumOfSequenceParameterSets); | |||||
WriteUInt16((ushort)configurationRecord.SPSBuffer.Length); | |||||
WriteArray(configurationRecord.SPSBuffer); | |||||
WriteByte(configurationRecord.NumOfPictureParameterSets); | |||||
WriteUInt16((ushort)configurationRecord.PPSBuffer.Length); | |||||
WriteArray(configurationRecord.PPSBuffer); | |||||
} | |||||
} | } | ||||
} | } |
@@ -39,10 +39,8 @@ namespace JT1078.Flv.Metadata | |||||
public byte AVCLevelIndication { get; set; } | public byte AVCLevelIndication { get; set; } | ||||
public int LengthSizeMinusOne { get; set; } | public int LengthSizeMinusOne { get; set; } | ||||
public int NumOfSequenceParameterSets { get; set; } | public int NumOfSequenceParameterSets { get; set; } | ||||
public List<SPSInfo> SPS { get; set; } | |||||
public byte[] SPSBuffer { get; set; } | public byte[] SPSBuffer { get; set; } | ||||
public byte NumOfPictureParameterSets { get; set; } = 1; | public byte NumOfPictureParameterSets { get; set; } = 1; | ||||
public List<PPSInfo> PPS { get; set; } | |||||
public byte[] PPSBuffer { get; set; } | public byte[] PPSBuffer { get; set; } | ||||
#region Just for non-spec-conform encoders ref:org.mp4parser.boxes.iso14496.part15.AvcDecoderConfigurationRecord | #region Just for non-spec-conform encoders ref:org.mp4parser.boxes.iso14496.part15.AvcDecoderConfigurationRecord | ||||
public const int LengthSizeMinusOnePaddingBits = 63; | public const int LengthSizeMinusOnePaddingBits = 63; | ||||
@@ -51,15 +49,5 @@ namespace JT1078.Flv.Metadata | |||||
public const int BitDepthLumaMinus8PaddingBits = 31; | public const int BitDepthLumaMinus8PaddingBits = 31; | ||||
public const int BitDepthChromaMinus8PaddingBits = 31; | public const int BitDepthChromaMinus8PaddingBits = 31; | ||||
#endregion | #endregion | ||||
public struct SPSInfo | |||||
{ | |||||
public ushort SequenceParameterSetLength { get; set; } | |||||
public byte[] SequenceParameterSetNALUnit { get; set; } | |||||
} | |||||
public struct PPSInfo | |||||
{ | |||||
public ushort PictureParameterSetLength { get; set; } | |||||
public byte[] PictureParameterSetNALUnit { get; set; } | |||||
} | |||||
} | } | ||||
} | } |
@@ -1,7 +1,7 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | <Project Sdk="Microsoft.NET.Sdk"> | ||||
<PropertyGroup> | <PropertyGroup> | ||||
<TargetFrameworks>netcoreapp2.2;net472</TargetFrameworks> | |||||
<TargetFrameworks>netcoreapp2.2;net472;netcoreapp3.0;</TargetFrameworks> | |||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | <AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||||
<PlatformTarget>AnyCPU</PlatformTarget> | <PlatformTarget>AnyCPU</PlatformTarget> | ||||
<OutputType>Exe</OutputType> | <OutputType>Exe</OutputType> | ||||
@@ -23,16 +23,28 @@ namespace JT1078.Protocol | |||||
{ | { | ||||
if (JT1078PackageGroupDict.TryGetValue(cacheKey, out var tmpPackage)) | if (JT1078PackageGroupDict.TryGetValue(cacheKey, out var tmpPackage)) | ||||
{ | { | ||||
tmpPackage.Bodies.Concat(jT1078Package.Bodies).ToArray(); | |||||
JT1078PackageGroupDict[cacheKey] = tmpPackage; | |||||
var totalLength = tmpPackage.Bodies.Length + jT1078Package.Bodies.Length; | |||||
byte[] poolBytes = JT1078ArrayPool.Rent(totalLength); | |||||
Span<byte> tmpSpan = poolBytes; | |||||
tmpPackage.Bodies.CopyTo(tmpSpan); | |||||
jT1078Package.Bodies.CopyTo(tmpSpan.Slice(tmpPackage.Bodies.Length)); | |||||
tmpPackage.Bodies= tmpSpan.Slice(0, totalLength).ToArray(); | |||||
JT1078ArrayPool.Return(poolBytes); | |||||
JT1078PackageGroupDict[cacheKey] = jT1078Package; | |||||
} | } | ||||
return default; | return default; | ||||
} | } | ||||
else if (jT1078Package.Label3.SubpackageType == JT1078SubPackageType.分包处理时的最后一个包) | else if (jT1078Package.Label3.SubpackageType == JT1078SubPackageType.分包处理时的最后一个包) | ||||
{ | { | ||||
if (JT1078PackageGroupDict.TryGetValue(cacheKey, out var tmpPackage)) | |||||
if(JT1078PackageGroupDict.TryRemove(cacheKey, out var tmpPackage)) | |||||
{ | { | ||||
tmpPackage.Bodies.Concat(jT1078Package.Bodies).ToArray(); | |||||
var totalLength = tmpPackage.Bodies.Length + jT1078Package.Bodies.Length; | |||||
byte[] poolBytes = JT1078ArrayPool.Rent(totalLength); | |||||
Span<byte> tmpSpan = poolBytes; | |||||
tmpPackage.Bodies.CopyTo(tmpSpan); | |||||
jT1078Package.Bodies.CopyTo(tmpSpan.Slice(tmpPackage.Bodies.Length)); | |||||
tmpPackage.Bodies = tmpSpan.Slice(0, totalLength).ToArray(); | |||||
JT1078ArrayPool.Return(poolBytes); | |||||
return tmpPackage; | return tmpPackage; | ||||
} | } | ||||
return default; | return default; | ||||
@@ -29,7 +29,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.Protocol.Extensions.W | |||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.Flv", "JT1078.Flv\JT1078.Flv.csproj", "{33E54FFC-7D91-42E5-9DC1-853738AB8980}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.Flv", "JT1078.Flv\JT1078.Flv.csproj", "{33E54FFC-7D91-42E5-9DC1-853738AB8980}" | ||||
EndProject | EndProject | ||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JT1078.Flv.Test", "JT1078.Flv.Test\JT1078.Flv.Test.csproj", "{D13FE092-1D11-4545-A322-9F06BCDAC0FD}" | |||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.Flv.Test", "JT1078.Flv.Test\JT1078.Flv.Test.csproj", "{D13FE092-1D11-4545-A322-9F06BCDAC0FD}" | |||||
EndProject | EndProject | ||||
Global | Global | ||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||