@@ -12,8 +12,8 @@ namespace JT1078.Flv.Test | |||||
public void FlvMuxer_Test_1() | public void FlvMuxer_Test_1() | ||||
{ | { | ||||
FlvEncoder encoder = new FlvEncoder(); | FlvEncoder encoder = new FlvEncoder(); | ||||
var buff = encoder.FlvFirstFrame(); | |||||
var hex= buff.ToHexString(); | |||||
//var buff = encoder.FlvFirstFrame(); | |||||
//var hex= buff.ToHexString(); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -1,4 +1,4 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<PropertyGroup> | <PropertyGroup> | ||||
<TargetFramework>netcoreapp3.0</TargetFramework> | <TargetFramework>netcoreapp3.0</TargetFramework> | ||||
@@ -15,10 +15,6 @@ | |||||
<ProjectReference Include="..\JT1078.Flv\JT1078.Flv.csproj" /> | <ProjectReference Include="..\JT1078.Flv\JT1078.Flv.csproj" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | |||||
<Folder Include="Metadata\" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | <ItemGroup> | ||||
<None Update="H264\JT1078.txt"> | <None Update="H264\JT1078.txt"> | ||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
@@ -18,14 +18,14 @@ namespace JT1078.Flv.Test.MessagePack | |||||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | ||||
FlvBody flvBody = new FlvBody() | FlvBody flvBody = new FlvBody() | ||||
{ | { | ||||
PreviousTagSize=0, | |||||
Tag = new FlvTags | |||||
{ | |||||
Type = Enums.TagType.ScriptData, | |||||
DataSize = 156, | |||||
DataTagsData = new Amf3 | |||||
{ | |||||
Amf3Metadatas = new List<Flv.Metadata.IAmf3Metadata> { | |||||
PreviousTagSize = 0, | |||||
Tag = new FlvTags | |||||
{ | |||||
Type = Enums.TagType.ScriptData, | |||||
DataSize = 156, | |||||
DataTagsData = new Amf3 | |||||
{ | |||||
Amf3Metadatas = new List<Flv.Metadata.IAmf3Metadata> { | |||||
new Amf3Metadata_Duration{ | new Amf3Metadata_Duration{ | ||||
Value=7.22100 | Value=7.22100 | ||||
}, | }, | ||||
@@ -48,11 +48,11 @@ namespace JT1078.Flv.Test.MessagePack | |||||
Value=544.00000 | Value=544.00000 | ||||
} | } | ||||
} | } | ||||
} | |||||
} | |||||
} | |||||
} | |||||
}; | }; | ||||
flvMessagePackWriter.WriteFlvBody(flvBody); | flvMessagePackWriter.WriteFlvBody(flvBody); | ||||
var hex=flvMessagePackWriter.FlushAndGetArray().ToHexString(); | |||||
var hex = flvMessagePackWriter.FlushAndGetArray().ToHexString(); | |||||
Assert.Equal("00000000120000A00000000000000002000A6F6E4D65746144617461080000000700086475726174696F6E00401CE24DD2F1A9FC000866696C6573697A6500413E99AD0000000000096672616D657261746500403D2AAAE297396D000668656967687400408E000000000000000C766964656F636F646563696400401C000000000000000D766964656F646174617261746500000000000000000000057769647468004081000000000000", hex); | Assert.Equal("00000000120000A00000000000000002000A6F6E4D65746144617461080000000700086475726174696F6E00401CE24DD2F1A9FC000866696C6573697A6500413E99AD0000000000096672616D657261746500403D2AAAE297396D000668656967687400408E000000000000000C766964656F636F646563696400401C000000000000000D766964656F646174617261746500000000000000000000057769647468004081000000000000", hex); | ||||
} | } | ||||
} | } | ||||
@@ -0,0 +1,41 @@ | |||||
using JT1078.Flv.MessagePack; | |||||
using JT1078.Flv.Metadata; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
using Xunit; | |||||
using JT1078.Flv.Extensions; | |||||
namespace JT1078.Flv.Test.Metadata | |||||
{ | |||||
public class AVCDecoderConfigurationRecordTest | |||||
{ | |||||
[Fact] | |||||
public void Test1() | |||||
{ | |||||
Span<byte> buffer = new byte[1024]; | |||||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | |||||
AVCDecoderConfigurationRecord aVCDecoderConfigurationRecord = new AVCDecoderConfigurationRecord(); | |||||
aVCDecoderConfigurationRecord.AVCProfileIndication = 0x64; | |||||
aVCDecoderConfigurationRecord.ProfileCompatibility = 0; | |||||
aVCDecoderConfigurationRecord.AVCLevelIndication =0x1F; | |||||
aVCDecoderConfigurationRecord.NumOfPictureParameterSets = 1; | |||||
aVCDecoderConfigurationRecord.SPSBuffer = new byte[] { | |||||
0x67,0x64,0x00,0x1F, | |||||
0xAC,0xD9,0x40,0x88, | |||||
0x1E,0x68,0x40,0x00, | |||||
0x00,0x03,0x01,0x80, | |||||
0x00,0x00,0x57,0x83, | |||||
0xC6,0x0C,0x65,0x80 | |||||
}; | |||||
aVCDecoderConfigurationRecord.PPSBuffer = new byte[] { | |||||
0x68,0xEB,0xE3,0xCB,0x22,0xC0 | |||||
}; | |||||
flvMessagePackWriter.WriteAVCDecoderConfigurationRecord(aVCDecoderConfigurationRecord); | |||||
var hexData = flvMessagePackWriter.FlushAndGetArray().ToHexString(); | |||||
Assert.Equal("0164001FFFE100186764001FACD940881E6840000003018000005783C60C658001000668EBE3CB22C0", hexData); | |||||
//0164001FFFE100186764001FACD940881E6840000003018000005783C60C658001000668EBE3CB22C0 | |||||
//0164001FFFE100186764001FACD940881E6840000003018000005783C60C658001000668EBE3CB22C0 | |||||
} | |||||
} | |||||
} |
@@ -1,6 +1,10 @@ | |||||
using JT1078.Flv.MessagePack; | |||||
using JT1078.Flv.Enums; | |||||
using JT1078.Flv.H264; | |||||
using JT1078.Flv.MessagePack; | |||||
using JT1078.Flv.Metadata; | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Linq; | |||||
using System.Text; | using System.Text; | ||||
namespace JT1078.Flv | namespace JT1078.Flv | ||||
@@ -8,7 +12,8 @@ namespace JT1078.Flv | |||||
public class FlvEncoder | public class FlvEncoder | ||||
{ | { | ||||
private static readonly FlvHeader VideoFlvHeader = new FlvHeader(true, false); | private static readonly FlvHeader VideoFlvHeader = new FlvHeader(true, false); | ||||
public byte[] FlvFirstFrame() | |||||
private static readonly H264Decoder h264Decoder = new H264Decoder(); | |||||
public byte[] FlvFirstFrame(List<H264NALU> nALUs) | |||||
{ | { | ||||
byte[] buffer = FlvArrayPool.Rent(10240); | byte[] buffer = FlvArrayPool.Rent(10240); | ||||
try | try | ||||
@@ -16,15 +21,142 @@ namespace JT1078.Flv | |||||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | ||||
//flv header | //flv header | ||||
flvMessagePackWriter.WriteArray(VideoFlvHeader.ToArray()); | flvMessagePackWriter.WriteArray(VideoFlvHeader.ToArray()); | ||||
//flv body | |||||
//SPS -> 7 | |||||
var spsNALU = nALUs.FirstOrDefault(n => n.NALUHeader.NalUnitType == 7); | |||||
spsNALU.RawData = h264Decoder.DiscardEmulationPreventionBytes(spsNALU.RawData); | |||||
ExpGolombReader h264GolombReader = new ExpGolombReader(spsNALU.RawData); | |||||
var spsInfo = h264GolombReader.ReadSPS(); | |||||
//PPS -> 8 | |||||
var ppsNALU = nALUs.FirstOrDefault(n => n.NALUHeader.NalUnitType == 8); | |||||
//IDR -> 5 关键帧 | |||||
var idrNALU = nALUs.FirstOrDefault(n => n.NALUHeader.NalUnitType == 5); | |||||
//SEI -> 6 | |||||
//var seiNALU = nALUs.FirstOrDefault(n => n.NALUHeader.NalUnitType == 6); | |||||
//flv body script tag | |||||
var scriptTag = CreateScriptTagFrame(spsInfo.width, spsInfo.height); | |||||
//flv body video tag | |||||
AVCDecoderConfigurationRecord aVCDecoderConfigurationRecord = new AVCDecoderConfigurationRecord(); | |||||
aVCDecoderConfigurationRecord.AVCProfileIndication = spsInfo.profileIdc; | |||||
aVCDecoderConfigurationRecord.ProfileCompatibility =(byte)spsInfo.profileCompat; | |||||
aVCDecoderConfigurationRecord.AVCLevelIndication = spsInfo.levelIdc; | |||||
aVCDecoderConfigurationRecord.NumOfPictureParameterSets = 1; | |||||
aVCDecoderConfigurationRecord.PPSBuffer = spsNALU.RawData; | |||||
aVCDecoderConfigurationRecord.SPSBuffer = ppsNALU.RawData; | |||||
var videoTag = CreateVideoTag0Frame((uint)scriptTag.Length, aVCDecoderConfigurationRecord); | |||||
return flvMessagePackWriter.FlushAndGetArray(); | |||||
} | |||||
finally | |||||
{ | |||||
FlvArrayPool.Return(buffer); | |||||
} | |||||
} | |||||
public byte[] CreateScriptTagFrame(int width,int height) | |||||
{ | |||||
byte[] buffer = FlvArrayPool.Rent(1024); | |||||
try | |||||
{ | |||||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | |||||
//flv body PreviousTagSize awalys 0 | //flv body PreviousTagSize awalys 0 | ||||
flvMessagePackWriter.WriteUInt32(0); | flvMessagePackWriter.WriteUInt32(0); | ||||
//flv body tag | |||||
//flv body script 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.Add(new Amf3Metadata_Duration | |||||
{ | |||||
Value = 0d | |||||
}); | |||||
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_VideoDataRate | |||||
{ | |||||
Value = 0d | |||||
}); | |||||
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_VideoCodecId | |||||
{ | |||||
Value = 7d | |||||
}); | |||||
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_FrameRate | |||||
{ | |||||
Value = 25d | |||||
}); | |||||
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_Width | |||||
{ | |||||
Value = width | |||||
}); | |||||
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_Height | |||||
{ | |||||
Value = height | |||||
}); | |||||
flvMessagePackWriter.WriteFlvTag(flvTags); | |||||
return flvMessagePackWriter.FlushAndGetArray(); | |||||
} | |||||
finally | |||||
{ | |||||
FlvArrayPool.Return(buffer); | |||||
} | |||||
} | |||||
public byte[] CreateVideoTag0Frame(uint previousTagSize,AVCDecoderConfigurationRecord aVCDecoderConfigurationRecord) | |||||
{ | |||||
byte[] buffer = FlvArrayPool.Rent(1024); | |||||
try | |||||
{ | |||||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | |||||
//flv body PreviousTagSize ScriptTag | |||||
flvMessagePackWriter.WriteUInt32(previousTagSize); | |||||
//flv body video 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 | //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; | |||||
flvTags.VideoTagsData.VideoData.AVCDecoderConfiguration = aVCDecoderConfigurationRecord; | |||||
flvMessagePackWriter.WriteFlvTag(flvTags); | |||||
return flvMessagePackWriter.FlushAndGetArray(); | |||||
} | |||||
finally | |||||
{ | |||||
FlvArrayPool.Return(buffer); | |||||
} | |||||
} | |||||
public byte[] CreateVideoTagOtherFrame(uint previousTagSize, H264NALU nALU) | |||||
{ | |||||
byte[] buffer = FlvArrayPool.Rent(2048); | |||||
try | |||||
{ | |||||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | |||||
//flv body PreviousTagSize ScriptTag | |||||
flvMessagePackWriter.WriteUInt32(previousTagSize); | |||||
//flv body video tag | |||||
//flv body tag header | |||||
FlvTags flvTags = new FlvTags(); | |||||
flvTags.Type = TagType.Video; | |||||
flvTags.Timestamp = nALU.LastIFrameInterval; | |||||
flvTags.TimestampExt = 0; | |||||
flvTags.StreamId = 0; | |||||
//flv body tag body | |||||
flvTags.VideoTagsData = new VideoTags(); | |||||
flvTags.VideoTagsData.FrameType = FrameType.InterFrame; | |||||
flvTags.VideoTagsData.VideoData = new AvcVideoPacke(); | |||||
flvTags.VideoTagsData.VideoData.AvcPacketType = AvcPacketType.Raw; | |||||
flvTags.VideoTagsData.VideoData.CompositionTime = nALU.LastIFrameInterval; | |||||
flvTags.VideoTagsData.VideoData.Data = nALU.RawData; | |||||
flvMessagePackWriter.WriteFlvTag(flvTags); | |||||
return flvMessagePackWriter.FlushAndGetArray(); | return flvMessagePackWriter.FlushAndGetArray(); | ||||
} | } | ||||
finally | finally | ||||
@@ -32,6 +164,7 @@ namespace JT1078.Flv | |||||
FlvArrayPool.Return(buffer); | FlvArrayPool.Return(buffer); | ||||
} | } | ||||
} | } | ||||
public byte[] FlvOtherFrame() | public byte[] FlvOtherFrame() | ||||
{ | { | ||||
byte[] buffer = FlvArrayPool.Rent(10240); | byte[] buffer = FlvArrayPool.Rent(10240); | ||||
@@ -80,13 +80,11 @@ namespace JT1078.Flv.MessagePack | |||||
videoPacke.CompositionTime = 0; | videoPacke.CompositionTime = 0; | ||||
WriteUInt24(videoPacke.CompositionTime); | WriteUInt24(videoPacke.CompositionTime); | ||||
//AVCDecoderConfigurationRecord | //AVCDecoderConfigurationRecord | ||||
#warning AVCDecoderConfigurationRecord | |||||
WriteArray(videoPacke.Data); | |||||
WriteAVCDecoderConfigurationRecord(videoPacke.AVCDecoderConfiguration); | |||||
} | } | ||||
else if(videoPacke.AvcPacketType == AvcPacketType.Raw) | else if(videoPacke.AvcPacketType == AvcPacketType.Raw) | ||||
{ | { | ||||
WriteUInt24(videoPacke.CompositionTime); | WriteUInt24(videoPacke.CompositionTime); | ||||
#warning One or more NALUs | |||||
//One or more NALUs | //One or more NALUs | ||||
WriteArray(videoPacke.Data); | WriteArray(videoPacke.Data); | ||||
} | } | ||||
@@ -104,7 +102,8 @@ namespace JT1078.Flv.MessagePack | |||||
WriteByte(configurationRecord.AVCProfileIndication); | WriteByte(configurationRecord.AVCProfileIndication); | ||||
WriteByte(configurationRecord.ProfileCompatibility); | WriteByte(configurationRecord.ProfileCompatibility); | ||||
WriteByte(configurationRecord.AVCLevelIndication); | WriteByte(configurationRecord.AVCLevelIndication); | ||||
WriteByte((byte)configurationRecord.LengthSizeMinusOne); | |||||
#warning reserved(6bits)+LengthSizeMinusOne(2bits) | |||||
WriteByte(0xFF); | |||||
WriteByte((byte)configurationRecord.NumOfSequenceParameterSets); | WriteByte((byte)configurationRecord.NumOfSequenceParameterSets); | ||||
WriteUInt16((ushort)configurationRecord.SPSBuffer.Length); | WriteUInt16((ushort)configurationRecord.SPSBuffer.Length); | ||||
WriteArray(configurationRecord.SPSBuffer); | WriteArray(configurationRecord.SPSBuffer); | ||||
@@ -38,9 +38,9 @@ namespace JT1078.Flv.Metadata | |||||
public byte ProfileCompatibility { get; set; } | public byte ProfileCompatibility { get; set; } | ||||
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; }= 0xE0 | 1; | |||||
public byte[] SPSBuffer { get; set; } | public byte[] SPSBuffer { get; set; } | ||||
public byte NumOfPictureParameterSets { get; set; } = 1; | |||||
public byte NumOfPictureParameterSets { 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; | ||||
@@ -11,6 +11,9 @@ namespace JT1078.Flv.Metadata | |||||
public ushort FieldNameLength { get; set; } | public ushort FieldNameLength { get; set; } | ||||
public string FieldName { get; set; } = "videocodecid"; | public string FieldName { get; set; } = "videocodecid"; | ||||
public byte DataType { get; set; } = 0x00; | public byte DataType { get; set; } = 0x00; | ||||
/// <summary> | |||||
/// <see cref="typeof(JT1078.Flv.Enums.CodecId.AvcVideoPacke)"/> | |||||
/// </summary> | |||||
public object Value { get; set; } | public object Value { get; set; } | ||||
public ReadOnlySpan<byte> ToBuffer() | public ReadOnlySpan<byte> ToBuffer() | ||||
@@ -11,6 +11,8 @@ namespace JT1078.Flv.Metadata | |||||
public uint CompositionTime { get; set; } | public uint CompositionTime { get; set; } | ||||
public AVCDecoderConfigurationRecord AVCDecoderConfiguration { get; set; } | |||||
public byte[] Data { get; set; } | public byte[] Data { get; set; } | ||||
} | } | ||||
} | } |