@@ -330,3 +330,4 @@ ASALocalRun/ | |||
.mfractor/ | |||
/src/JT1078.Flv.Test/H264/JT1078_3.txt | |||
/src/JT1078.Flv.Test/H264/JT1078_4.txt | |||
/src/JT1078.Flv.Test/H264/JT1078_5.txt |
@@ -8,6 +8,7 @@ using System.IO; | |||
using System.Linq; | |||
using JT1078.Protocol.Enums; | |||
using JT1078.Flv.H264; | |||
using JT1078.Flv.MessagePack; | |||
namespace JT1078.Flv.Test | |||
{ | |||
@@ -208,5 +209,67 @@ namespace JT1078.Flv.Test | |||
fileStream?.Dispose(); | |||
} | |||
} | |||
[Fact] | |||
public void FlvEncoder_Test_5() | |||
{ | |||
FileStream fileStream = null; | |||
Flv.H264.H264Decoder decoder = new Flv.H264.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; | |||
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>(); | |||
foreach (var item in tmp1) | |||
{ | |||
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(); | |||
} | |||
} | |||
} | |||
} |
@@ -8,27 +8,21 @@ | |||
</head> | |||
<body> | |||
<video muted="muted" webkit-playsinline="true" autoplay="true" id="player"></video> | |||
<video muted="muted" webkit-playsinline="true" autoplay="true" id="player2"></video> | |||
<script> | |||
if (flvjs.isSupported()) { | |||
var player = document.getElementById('player'); | |||
var flvPlayer = flvjs.createPlayer({ | |||
type: 'flv', | |||
url: "jt1078_3.flv" | |||
url: "jt1078_5.flv" | |||
}); | |||
var width, height, flag; | |||
flvPlayer.attachMediaElement(player); | |||
flvPlayer.on(flvjs.Events.SCRIPTDATA_ARRIVED, (e) => { | |||
console.log(e); | |||
}); | |||
flvPlayer.load(); | |||
flvPlayer.play(); | |||
var player2 = document.getElementById('player2'); | |||
var flvPlayer2 = flvjs.createPlayer({ | |||
type: 'flv', | |||
url: "jt1078_4.flv" | |||
}); | |||
flvPlayer2.attachMediaElement(player2); | |||
flvPlayer2.load(); | |||
flvPlayer2.play(); | |||
} | |||
</script> | |||
</body> |
@@ -7,6 +7,7 @@ | |||
<ItemGroup> | |||
<None Remove="H264\JT1078_3.rar" /> | |||
<None Remove="H264\JT1078_4.rar" /> | |||
<None Remove="H264\JT1078_5.rar" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
@@ -39,6 +40,9 @@ | |||
<None Update="H264\JT1078_4.txt"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
<None Update="H264\JT1078_5.txt"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
</ItemGroup> | |||
</Project> |
@@ -17,6 +17,7 @@ namespace JT1078.Flv | |||
private const uint PreviousTagSizeFixedLength = 4; | |||
private static readonly ConcurrentDictionary<string, uint> PreviousTagSizeDict; | |||
private static readonly ConcurrentDictionary<string, bool> FrameInitDict; | |||
private static readonly ConcurrentDictionary<string, SPSInfo> VideoSPSDict; | |||
private static readonly Flv.H264.H264Decoder H264Decoder; | |||
static FlvEncoder() | |||
{ | |||
@@ -24,6 +25,7 @@ namespace JT1078.Flv | |||
VideoFlvHeaderBuffer = VideoFlvHeader.ToArray().ToArray(); | |||
PreviousTagSizeDict = new ConcurrentDictionary<string, uint>(); | |||
FrameInitDict = new ConcurrentDictionary<string, bool>(); | |||
VideoSPSDict = new ConcurrentDictionary<string, SPSInfo>(); | |||
H264Decoder = new Flv.H264.H264Decoder(); | |||
} | |||
/// <summary> | |||
@@ -32,7 +34,7 @@ namespace JT1078.Flv | |||
/// <param name="sps">NalUnitType->7</param> | |||
/// <param name="pps">NalUnitType->8</param> | |||
/// <returns></returns> | |||
public byte[] CreateFlvFirstFrame(H264NALU sps, H264NALU pps) | |||
public byte[] CreateFlvFirstFrame(H264NALU sps, H264NALU pps,bool isSendFlvHeader=true,uint previousTagSize=0) | |||
{ | |||
byte[] buffer = FlvArrayPool.Rent(65535); | |||
int currentMarkPosition = 0; | |||
@@ -40,16 +42,18 @@ namespace JT1078.Flv | |||
try | |||
{ | |||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | |||
//flv header | |||
flvMessagePackWriter.WriteArray(VideoFlvHeaderBuffer); | |||
if (isSendFlvHeader) | |||
{ | |||
//flv header | |||
flvMessagePackWriter.WriteArray(VideoFlvHeaderBuffer); | |||
} | |||
//SPS -> 7 | |||
ExpGolombReader h264GolombReader = new ExpGolombReader(sps.RawData); | |||
var spsInfo = h264GolombReader.ReadSPS(); | |||
currentMarkPosition = flvMessagePackWriter.GetCurrentPosition(); | |||
//flv body script tag | |||
CreateScriptTagFrame(ref flvMessagePackWriter, spsInfo.width, spsInfo.height); | |||
CreateScriptTagFrame(ref flvMessagePackWriter, spsInfo.width, spsInfo.height, previousTagSize); | |||
nextMarkPosition = flvMessagePackWriter.GetCurrentPosition(); | |||
//flv body video tag | |||
@@ -78,10 +82,10 @@ namespace JT1078.Flv | |||
FlvArrayPool.Return(buffer); | |||
} | |||
} | |||
private void CreateScriptTagFrame(ref FlvMessagePackWriter flvMessagePackWriter, int width, int height,double frameRate = 25d) | |||
private void CreateScriptTagFrame(ref FlvMessagePackWriter flvMessagePackWriter, int width, int height,uint previousTagSize, double frameRate = 25d) | |||
{ | |||
//flv body PreviousTagSize awalys 0 | |||
flvMessagePackWriter.WriteUInt32(0); | |||
flvMessagePackWriter.WriteUInt32(previousTagSize); | |||
//flv body script tag | |||
//flv body tag header | |||
FlvTags flvTags = new FlvTags(); | |||
@@ -141,7 +145,7 @@ namespace JT1078.Flv | |||
public static uint LastFrameInterval = 0; | |||
private void CreateVideoTagOtherFrame(ref FlvMessagePackWriter flvMessagePackWriter, uint previousTagSize, H264NALU nALU, H264NALU sps, H264NALU pps,H264NALU sei) | |||
private void CreateVideoTagOtherFrame(ref FlvMessagePackWriter flvMessagePackWriter, uint previousTagSize, H264NALU nALU) | |||
{ | |||
//flv body PreviousTagSize | |||
flvMessagePackWriter.WriteUInt32(previousTagSize); | |||
@@ -156,38 +160,18 @@ namespace JT1078.Flv | |||
flvTags.VideoTagsData.VideoData = new AvcVideoPacke(); | |||
flvTags.VideoTagsData.VideoData.CompositionTime = 0; | |||
flvTags.VideoTagsData.VideoData.AvcPacketType = AvcPacketType.Raw; | |||
flvTags.VideoTagsData.VideoData.MultiData = new List<byte[]>(); | |||
LastFrameInterval += nALU.LastFrameInterval; | |||
flvTags.Timestamp = LastFrameInterval; | |||
if (nALU.NALUHeader.NalUnitType == 5) | |||
{ | |||
flvTags.VideoTagsData.FrameType = FrameType.KeyFrame; | |||
if (sps!=null && pps != null) | |||
{ | |||
//flvTags.VideoTagsData.VideoData.MultiData.Add(sps.RawData); | |||
//flvTags.VideoTagsData.VideoData.MultiData.Add(pps.RawData); | |||
flvTags.VideoTagsData.VideoData.MultiData.Add(nALU.RawData); | |||
} | |||
else | |||
{ | |||
//if (sei != null) | |||
//{ | |||
// flvTags.VideoTagsData.VideoData.MultiData.Add(sei.RawData); | |||
//} | |||
flvTags.VideoTagsData.VideoData.MultiData.Add(nALU.RawData); | |||
} | |||
} | |||
else | |||
{ | |||
flvTags.VideoTagsData.FrameType = FrameType.InterFrame; | |||
//if (sei != null) | |||
//{ | |||
// flvTags.VideoTagsData.VideoData.MultiData.Add(sei.RawData); | |||
//} | |||
flvTags.VideoTagsData.VideoData.MultiData.Add(nALU.RawData); | |||
} | |||
flvTags.VideoTagsData.VideoData.Data = nALU.RawData; | |||
flvMessagePackWriter.WriteFlvTag(flvTags); | |||
} | |||
public byte[] CreateFlvFrame(List<H264NALU> nALUs, int minimumLength = 65535) | |||
@@ -204,10 +188,22 @@ namespace JT1078.Flv | |||
if (sps!=null && pps != null) | |||
{ | |||
string key = sps.GetKey(); | |||
if (!FrameInitDict.ContainsKey(key)) | |||
ExpGolombReader h264GolombReader = new ExpGolombReader(sps.RawData); | |||
var spsInfo = h264GolombReader.ReadSPS(); | |||
if(VideoSPSDict.TryGetValue(key,out var spsInfoCache)) | |||
{ | |||
if(spsInfoCache.height!= spsInfo.height && spsInfoCache.width!= spsInfo.width) | |||
{ | |||
uint previousTagSize = 0; | |||
PreviousTagSizeDict.TryGetValue(key, out previousTagSize); | |||
flvMessagePackWriter.WriteArray(CreateFlvFirstFrame(sps, pps,false, previousTagSize)); | |||
VideoSPSDict.TryUpdate(key, spsInfo, spsInfo); | |||
} | |||
} | |||
else | |||
{ | |||
flvMessagePackWriter.WriteArray(CreateFlvFirstFrame(sps, pps)); | |||
FrameInitDict.TryAdd(key, true); | |||
VideoSPSDict.TryAdd(key, spsInfo); | |||
} | |||
} | |||
foreach (var naln in nALUs.Where(w=> w.NALUHeader.NalUnitType != 7 && w.NALUHeader.NalUnitType != 8 && w.NALUHeader.NalUnitType != 6)) | |||
@@ -216,7 +212,7 @@ namespace JT1078.Flv | |||
if (PreviousTagSizeDict.TryGetValue(key, out uint previousTagSize)) | |||
{ | |||
currentMarkPosition = flvMessagePackWriter.GetCurrentPosition(); | |||
CreateVideoTagOtherFrame(ref flvMessagePackWriter, previousTagSize, naln,sps, pps, sei); | |||
CreateVideoTagOtherFrame(ref flvMessagePackWriter, previousTagSize, naln); | |||
nextMarkPosition = flvMessagePackWriter.GetCurrentPosition(); | |||
uint tmpPreviousTagSize = (uint)(nextMarkPosition - currentMarkPosition - PreviousTagSizeFixedLength); | |||
PreviousTagSizeDict.TryUpdate(key, tmpPreviousTagSize, previousTagSize); | |||
@@ -21,7 +21,7 @@ namespace JT1078.Flv.MessagePack | |||
Word = 0; | |||
BitsAvailable = 0; | |||
} | |||
public (byte profileIdc,byte levelIdc,uint profileCompat,int width, int height) ReadSPS() | |||
public SPSInfo ReadSPS() | |||
{ | |||
int sarScale = 1; | |||
uint frameCropLeftOffset=0; | |||
@@ -153,7 +153,7 @@ namespace JT1078.Flv.MessagePack | |||
} | |||
int width= (int)((((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale); | |||
int height = (int)(((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - ((frameMbsOnlyFlag == 1U ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset))); | |||
return (profileIdc, levelIdc, profileCompat,width, height); | |||
return new SPSInfo { profileIdc= profileIdc, levelIdc= levelIdc, profileCompat= profileCompat, width= width, height= height }; | |||
} | |||
public void LoadWord() | |||
{ | |||
@@ -310,4 +310,13 @@ namespace JT1078.Flv.MessagePack | |||
} | |||
} | |||
} | |||
public struct SPSInfo | |||
{ | |||
public byte profileIdc { get; set; } | |||
public byte levelIdc { get; set; } | |||
public uint profileCompat { get; set; } | |||
public int width { get; set; } | |||
public int height { get; set; } | |||
} | |||
} |
@@ -85,19 +85,6 @@ namespace JT1078.Flv.MessagePack | |||
else if(videoPacke.AvcPacketType == AvcPacketType.Raw) | |||
{ | |||
WriteUInt24(videoPacke.CompositionTime); | |||
//One or more NALUs | |||
//WriteArray(new byte[] {0,0,0,1}); | |||
if (videoPacke.MultiData != null) | |||
{ | |||
foreach(var item in videoPacke.MultiData) | |||
{ | |||
if (item != null && item.Length > 0) | |||
{ | |||
WriteInt32(item.Length); | |||
WriteArray(item); | |||
} | |||
} | |||
} | |||
if (videoPacke.Data != null && videoPacke.Data.Length>0) | |||
{ | |||
WriteInt32(videoPacke.Data.Length); | |||
@@ -121,19 +108,10 @@ namespace JT1078.Flv.MessagePack | |||
//reserved(6bits)+LengthSizeMinusOne(2bits) | |||
WriteByte(0xFF); | |||
WriteByte((byte)configurationRecord.NumOfSequenceParameterSets); | |||
WriteUInt16((ushort)(configurationRecord.SPSBuffer.Length)); | |||
//WriteUInt16((ushort)(configurationRecord.SPSBuffer.Length + 4)); | |||
//WriteArray(new byte[] { 0, 0, 0, 1 }); | |||
WriteArray(configurationRecord.SPSBuffer); | |||
WriteByte(configurationRecord.NumOfPictureParameterSets); | |||
WriteUInt16((ushort)(configurationRecord.PPSBuffer.Length)); | |||
//WriteUInt16((ushort)(configurationRecord.PPSBuffer.Length+4)); | |||
//WriteArray(new byte[] { 0, 0, 0, 1 }); | |||
WriteArray(configurationRecord.PPSBuffer); | |||
} | |||
} | |||
@@ -14,7 +14,5 @@ namespace JT1078.Flv.Metadata | |||
public AVCDecoderConfigurationRecord AVCDecoderConfiguration { get; set; } | |||
public byte[] Data { get; set; } | |||
public List<byte[]> MultiData { get; set; } | |||
} | |||
} |
@@ -24,40 +24,22 @@ namespace JT1078.Protocol.MessagePack | |||
} | |||
public void WriteUInt16(ushort value) | |||
{ | |||
var span = writer.Free; | |||
span[0] = (byte)(value >> 8); | |||
span[1] = (byte)value; | |||
BinaryPrimitives.WriteUInt16BigEndian(writer.Free, value); | |||
writer.Advance(2); | |||
} | |||
public void WriteInt32(int value) | |||
{ | |||
var span = writer.Free; | |||
span[0] = (byte)(value >> 24); | |||
span[1] = (byte)(value >> 16); | |||
span[2] = (byte)(value >> 8); | |||
span[3] = (byte)value; | |||
BinaryPrimitives.WriteInt32BigEndian(writer.Free, value); | |||
writer.Advance(4); | |||
} | |||
public void WriteUInt64(ulong value) | |||
{ | |||
var span = writer.Free; | |||
span[0] = (byte)(value >> 56); | |||
span[1] = (byte)(value >> 48); | |||
span[2] = (byte)(value >> 40); | |||
span[3] = (byte)(value >> 32); | |||
span[4] = (byte)(value >> 24); | |||
span[5] = (byte)(value >> 16); | |||
span[6] = (byte)(value >> 8); | |||
span[7] = (byte)value; | |||
BinaryPrimitives.WriteUInt64BigEndian(writer.Free, value); | |||
writer.Advance(8); | |||
} | |||
public void WriteUInt32(uint value) | |||
{ | |||
var span = writer.Free; | |||
span[0] = (byte)(value >> 24); | |||
span[1] = (byte)(value >> 16); | |||
span[2] = (byte)(value >> 8); | |||
span[3] = (byte)value; | |||
BinaryPrimitives.WriteUInt32BigEndian(writer.Free, value); | |||
writer.Advance(4); | |||
} | |||
public void WriteArray(ReadOnlySpan<byte> src) | |||