using JT1078.Flv.Enums; using JT1078.Flv.H264; using JT1078.Flv.MessagePack; using JT1078.Flv.Metadata; using JT1078.Protocol.Enums; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; namespace JT1078.Flv { public class FlvEncoder { struct FlvFrameInfo { public uint PreviousTagSize { get; set;} public uint LastFrameInterval { get; set; } } public static readonly byte[] VideoFlvHeaderBuffer; private const uint PreviousTagSizeFixedLength = 4; private static readonly ConcurrentDictionary VideoSPSDict; private static readonly Flv.H264.H264Decoder H264Decoder; private static readonly ConcurrentDictionary FlvFrameInfoDict; static FlvEncoder() { FlvHeader VideoFlvHeader = new FlvHeader(true, false); VideoFlvHeaderBuffer = VideoFlvHeader.ToArray().ToArray(); VideoSPSDict = new ConcurrentDictionary(); FlvFrameInfoDict = new ConcurrentDictionary(); H264Decoder = new Flv.H264.H264Decoder(); } /// /// /// private void CreateFlvKeyFrame(ref FlvMessagePackWriter flvMessagePackWriter, string key,byte[] spsRawData, byte[] ppsRawData, SPSInfo spsInfo, uint previousTagSize = 0) { int currentMarkPosition = 0; int nextMarkPosition = 0; currentMarkPosition = flvMessagePackWriter.GetCurrentPosition(); //flv body script tag CreateScriptTagFrame(ref flvMessagePackWriter, spsInfo.width, spsInfo.height, previousTagSize); nextMarkPosition = flvMessagePackWriter.GetCurrentPosition(); //flv body video tag uint scriptTagFramePreviousTagSize = (uint)(nextMarkPosition - currentMarkPosition - PreviousTagSizeFixedLength); AVCDecoderConfigurationRecord aVCDecoderConfigurationRecord = new AVCDecoderConfigurationRecord(); aVCDecoderConfigurationRecord.AVCProfileIndication = spsInfo.profileIdc; aVCDecoderConfigurationRecord.ProfileCompatibility = (byte)spsInfo.profileCompat; aVCDecoderConfigurationRecord.AVCLevelIndication = spsInfo.levelIdc; aVCDecoderConfigurationRecord.NumOfPictureParameterSets = 1; aVCDecoderConfigurationRecord.PPSBuffer = ppsRawData; aVCDecoderConfigurationRecord.SPSBuffer = spsRawData; currentMarkPosition = flvMessagePackWriter.GetCurrentPosition(); CreateVideoTag0Frame(ref flvMessagePackWriter, scriptTagFramePreviousTagSize, aVCDecoderConfigurationRecord); nextMarkPosition = flvMessagePackWriter.GetCurrentPosition(); //flv body video tag 0 uint videoTag0PreviousTagSize = (uint)(nextMarkPosition - currentMarkPosition - PreviousTagSizeFixedLength); //cache PreviousTagSize FlvFrameInfoDict.AddOrUpdate(key, new FlvFrameInfo { PreviousTagSize= videoTag0PreviousTagSize }, (a, b) => { b.PreviousTagSize = videoTag0PreviousTagSize; return b; }); } private void CreateScriptTagFrame(ref FlvMessagePackWriter flvMessagePackWriter, int width, int height,uint previousTagSize, double frameRate = 25d) { //flv body PreviousTagSize awalys 0 flvMessagePackWriter.WriteUInt32(previousTagSize); //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 = new List(); 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 = frameRate }); flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_Width { Value = width }); flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_Height { Value = height }); flvMessagePackWriter.WriteFlvTag(flvTags); } private void CreateVideoTag0Frame(ref FlvMessagePackWriter flvMessagePackWriter, uint previousTagSize, AVCDecoderConfigurationRecord aVCDecoderConfigurationRecord) { //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); } private void CreateVideoTagOtherFrame(ref FlvMessagePackWriter flvMessagePackWriter, FlvFrameInfo flvFrameInfo, H264NALU nALU) { //flv body PreviousTagSize flvMessagePackWriter.WriteUInt32(flvFrameInfo.PreviousTagSize); //flv body video tag //flv body tag header FlvTags flvTags = new FlvTags(); flvTags.Type = TagType.Video; flvTags.TimestampExt = 0; flvTags.StreamId = 0; //flv body tag body flvTags.VideoTagsData = new VideoTags(); flvTags.VideoTagsData.VideoData = new AvcVideoPacke(); flvTags.VideoTagsData.VideoData.CompositionTime = 0; flvTags.VideoTagsData.VideoData.AvcPacketType = AvcPacketType.Raw; flvTags.Timestamp = flvFrameInfo.LastFrameInterval; if (nALU.NALUHeader.NalUnitType == 5 || nALU.DataType== JT1078DataType.视频I帧) { flvTags.VideoTagsData.FrameType = FrameType.KeyFrame; } else { flvTags.VideoTagsData.FrameType = FrameType.InterFrame; } flvTags.VideoTagsData.VideoData.Data = nALU.RawData; flvMessagePackWriter.WriteFlvTag(flvTags); } public byte[] CreateFlvFrame(List nALUs, int minimumLength = 65535) { byte[] buffer = FlvArrayPool.Rent(minimumLength); try { FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); int currentMarkPosition = 0; int nextMarkPosition = 0; H264NALU sps=null, pps=null; SPSInfo spsInfo = new SPSInfo(); foreach (var naln in nALUs) { string key = naln.GetKey(); if (sps != null && pps != null) { if (VideoSPSDict.TryGetValue(key, out var spsInfoCache)) { if (spsInfoCache.height != spsInfo.height && spsInfoCache.width != spsInfo.width) { if(FlvFrameInfoDict.TryGetValue(key, out FlvFrameInfo flvFrameInfo)) { CreateFlvKeyFrame(ref flvMessagePackWriter, key, sps.RawData, pps.RawData, spsInfo, flvFrameInfo.PreviousTagSize); VideoSPSDict.TryUpdate(key, spsInfo, spsInfo); flvFrameInfo.LastFrameInterval = 0; FlvFrameInfoDict.TryUpdate(key, flvFrameInfo, flvFrameInfo); } } } else { CreateFlvKeyFrame(ref flvMessagePackWriter, key, sps.RawData, pps.RawData, spsInfo, 0); VideoSPSDict.TryAdd(key, spsInfo); } } //7 8 6 5 1 1 1 1 7 8 6 5 1 1 1 1 1 7 8 6 5 1 1 1 1 1 switch (naln.NALUHeader.NalUnitType) { case 5:// IDR case 1:// I/P/B if (FlvFrameInfoDict.TryGetValue(key, out FlvFrameInfo flvFrameInfo)) { currentMarkPosition = flvMessagePackWriter.GetCurrentPosition(); CreateVideoTagOtherFrame(ref flvMessagePackWriter, flvFrameInfo, naln); nextMarkPosition = flvMessagePackWriter.GetCurrentPosition(); uint tmpPreviousTagSize = (uint)(nextMarkPosition - currentMarkPosition - PreviousTagSizeFixedLength); flvFrameInfo.PreviousTagSize = tmpPreviousTagSize; flvFrameInfo.LastFrameInterval += naln.LastFrameInterval; FlvFrameInfoDict.TryUpdate(key, flvFrameInfo, flvFrameInfo); } break; case 7:// sps sps = naln; var rawData = H264Decoder.DiscardEmulationPreventionBytes(naln.RawData); ExpGolombReader h264GolombReader = new ExpGolombReader(rawData); spsInfo = h264GolombReader.ReadSPS(); break; case 8:// pps pps = naln; break; case 6://SEI break; default: break; } } return flvMessagePackWriter.FlushAndGetArray(); } finally { FlvArrayPool.Return(buffer); } } } }