2.增加H264NALU的解析及创建 3.增加1078组包改用byte池tags/v1.1.0
@@ -7,7 +7,7 @@ namespace JT1078.Flv | |||
{ | |||
public class FlvMuxer | |||
{ | |||
private readonly FlvHeader VideoFlvHeader = new FlvHeader(true, false); | |||
private static readonly FlvHeader VideoFlvHeader = new FlvHeader(true, false); | |||
public byte[] FlvFirstFrame() | |||
{ | |||
byte[] buffer = FlvArrayPool.Rent(10240); | |||
@@ -17,7 +17,7 @@ namespace JT1078.Flv | |||
//flv header | |||
flvMessagePackWriter.WriteArray(VideoFlvHeader.ToArray()); | |||
//flv body | |||
//flv body PreviousTagSize | |||
//flv body PreviousTagSize awalys 0 | |||
flvMessagePackWriter.WriteUInt32(0); | |||
//flv body tag | |||
@@ -32,5 +32,28 @@ namespace JT1078.Flv | |||
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 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> | |||
/// 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 byte[] StartCodePrefix { 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++) | |||
{ | |||
if (ReadBoolean()) | |||
{ // seq_scaling_list_present_flag[ i ] | |||
{ | |||
// seq_scaling_list_present_flag[ i ] | |||
if (i < 6) | |||
{ | |||
SkipScalingList(16); | |||
@@ -97,5 +97,20 @@ namespace JT1078.Flv.MessagePack | |||
//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 int LengthSizeMinusOne { get; set; } | |||
public int NumOfSequenceParameterSets { get; set; } | |||
public List<SPSInfo> SPS { get; set; } | |||
public byte[] SPSBuffer { get; set; } | |||
public byte NumOfPictureParameterSets { get; set; } = 1; | |||
public List<PPSInfo> PPS { get; set; } | |||
public byte[] PPSBuffer { get; set; } | |||
#region Just for non-spec-conform encoders ref:org.mp4parser.boxes.iso14496.part15.AvcDecoderConfigurationRecord | |||
public const int LengthSizeMinusOnePaddingBits = 63; | |||
@@ -51,15 +49,5 @@ namespace JT1078.Flv.Metadata | |||
public const int BitDepthLumaMinus8PaddingBits = 31; | |||
public const int BitDepthChromaMinus8PaddingBits = 31; | |||
#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"> | |||
<PropertyGroup> | |||
<TargetFrameworks>netcoreapp2.2;net472</TargetFrameworks> | |||
<TargetFrameworks>netcoreapp2.2;net472;netcoreapp3.0;</TargetFrameworks> | |||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | |||
<PlatformTarget>AnyCPU</PlatformTarget> | |||
<OutputType>Exe</OutputType> | |||
@@ -23,16 +23,28 @@ namespace JT1078.Protocol | |||
{ | |||
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; | |||
} | |||
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 default; | |||
@@ -29,7 +29,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.Protocol.Extensions.W | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.Flv", "JT1078.Flv\JT1078.Flv.csproj", "{33E54FFC-7D91-42E5-9DC1-853738AB8980}" | |||
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 | |||
Global | |||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||