@@ -12,8 +12,8 @@ namespace JT1078.Flv.Test | |||
public void FlvMuxer_Test_1() | |||
{ | |||
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> | |||
<TargetFramework>netcoreapp3.0</TargetFramework> | |||
@@ -15,10 +15,6 @@ | |||
<ProjectReference Include="..\JT1078.Flv\JT1078.Flv.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Folder Include="Metadata\" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Update="H264\JT1078.txt"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
@@ -18,14 +18,14 @@ namespace JT1078.Flv.Test.MessagePack | |||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | |||
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{ | |||
Value=7.22100 | |||
}, | |||
@@ -48,11 +48,11 @@ namespace JT1078.Flv.Test.MessagePack | |||
Value=544.00000 | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
}; | |||
flvMessagePackWriter.WriteFlvBody(flvBody); | |||
var hex=flvMessagePackWriter.FlushAndGetArray().ToHexString(); | |||
var hex = flvMessagePackWriter.FlushAndGetArray().ToHexString(); | |||
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.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
namespace JT1078.Flv | |||
@@ -8,7 +12,8 @@ namespace JT1078.Flv | |||
public class FlvEncoder | |||
{ | |||
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); | |||
try | |||
@@ -16,15 +21,142 @@ namespace JT1078.Flv | |||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | |||
//flv header | |||
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 | |||
flvMessagePackWriter.WriteUInt32(0); | |||
//flv body tag | |||
//flv body script tag | |||
//flv body tag header | |||
FlvTags flvTags = new FlvTags(); | |||
flvTags.Type = TagType.ScriptData; | |||
flvTags.Timestamp = 0; | |||
flvTags.TimestampExt = 0; | |||
flvTags.StreamId = 0; | |||
//flv body tag body | |||
flvTags.DataTagsData = new Amf3(); | |||
flvTags.DataTagsData.Amf3Metadatas.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 | |||
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(); | |||
} | |||
finally | |||
@@ -32,6 +164,7 @@ namespace JT1078.Flv | |||
FlvArrayPool.Return(buffer); | |||
} | |||
} | |||
public byte[] FlvOtherFrame() | |||
{ | |||
byte[] buffer = FlvArrayPool.Rent(10240); | |||
@@ -80,13 +80,11 @@ namespace JT1078.Flv.MessagePack | |||
videoPacke.CompositionTime = 0; | |||
WriteUInt24(videoPacke.CompositionTime); | |||
//AVCDecoderConfigurationRecord | |||
#warning AVCDecoderConfigurationRecord | |||
WriteArray(videoPacke.Data); | |||
WriteAVCDecoderConfigurationRecord(videoPacke.AVCDecoderConfiguration); | |||
} | |||
else if(videoPacke.AvcPacketType == AvcPacketType.Raw) | |||
{ | |||
WriteUInt24(videoPacke.CompositionTime); | |||
#warning One or more NALUs | |||
//One or more NALUs | |||
WriteArray(videoPacke.Data); | |||
} | |||
@@ -104,7 +102,8 @@ namespace JT1078.Flv.MessagePack | |||
WriteByte(configurationRecord.AVCProfileIndication); | |||
WriteByte(configurationRecord.ProfileCompatibility); | |||
WriteByte(configurationRecord.AVCLevelIndication); | |||
WriteByte((byte)configurationRecord.LengthSizeMinusOne); | |||
#warning reserved(6bits)+LengthSizeMinusOne(2bits) | |||
WriteByte(0xFF); | |||
WriteByte((byte)configurationRecord.NumOfSequenceParameterSets); | |||
WriteUInt16((ushort)configurationRecord.SPSBuffer.Length); | |||
WriteArray(configurationRecord.SPSBuffer); | |||
@@ -38,9 +38,9 @@ namespace JT1078.Flv.Metadata | |||
public byte ProfileCompatibility { get; set; } | |||
public byte AVCLevelIndication { 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 NumOfPictureParameterSets { get; set; } = 1; | |||
public byte NumOfPictureParameterSets { 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; | |||
@@ -11,6 +11,9 @@ namespace JT1078.Flv.Metadata | |||
public ushort FieldNameLength { get; set; } | |||
public string FieldName { get; set; } = "videocodecid"; | |||
public byte DataType { get; set; } = 0x00; | |||
/// <summary> | |||
/// <see cref="typeof(JT1078.Flv.Enums.CodecId.AvcVideoPacke)"/> | |||
/// </summary> | |||
public object Value { get; set; } | |||
public ReadOnlySpan<byte> ToBuffer() | |||
@@ -11,6 +11,8 @@ namespace JT1078.Flv.Metadata | |||
public uint CompositionTime { get; set; } | |||
public AVCDecoderConfigurationRecord AVCDecoderConfiguration { get; set; } | |||
public byte[] Data { get; set; } | |||
} | |||
} |