using JT1078.FMp4.Enums; using JT1078.FMp4.MessagePack; using JT1078.FMp4.Samples; using JT1078.Protocol; using JT1078.Protocol.Enums; using JT1078.Protocol.H264; using JT1078.Protocol.MessagePack; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace JT1078.FMp4 { /// /// FMp4编码 /// fmp4 /// stream data /// ftyp /// moov /// styp 1 /// moof 1 /// mdat 1 /// ... /// styp n /// moof n /// mdat n /// mfra /// ref: https://www.w3.org/TR/mse-byte-stream-format-isobmff/#movie-fragment-relative-addressing /// public class FMp4Encoder { Dictionary TrackInfos; const uint DefaultSampleDuration = 40u; const uint DefaultSampleFlags = 0x1010000; const uint FirstSampleFlags = 33554432; const uint TfhdFlags = 0x2003a; //const uint TrunFlags = 0x205; const uint TrunFlags = 0x205; const uint SampleDescriptionIndex = 1; const uint TrackID = 1; /// /// /// public FMp4Encoder() { TrackInfos = new Dictionary(StringComparer.OrdinalIgnoreCase); } /// /// 编码ftyp盒子 /// /// public byte[] FtypBox() { byte[] buffer = FMp4ArrayPool.Rent(1024); FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); try { //ftyp FileTypeBox fileTypeBox = new FileTypeBox(); fileTypeBox.MajorBrand = "isom"; fileTypeBox.MinorVersion = "\0\0\u0002\0"; fileTypeBox.CompatibleBrands.Add("isom"); fileTypeBox.CompatibleBrands.Add("iso2"); fileTypeBox.CompatibleBrands.Add("avc1"); fileTypeBox.CompatibleBrands.Add("mp41"); fileTypeBox.CompatibleBrands.Add("iso5"); fileTypeBox.CompatibleBrands.Add("iso6"); fileTypeBox.ToBuffer(ref writer); var data = writer.FlushAndGetArray(); return data; } finally { FMp4ArrayPool.Return(buffer); } } /// /// 编码moov盒子 /// /// public byte[] VideoMoovBox(in H264NALU sps, in H264NALU pps) { byte[] buffer = FMp4ArrayPool.Rent(sps.RawData.Length + pps.RawData.Length + 1024); FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); try { ExpGolombReader h264GolombReader = new ExpGolombReader(sps.RawData); var spsInfo = h264GolombReader.ReadSPS(); //moov MovieBox movieBox = new MovieBox(); movieBox.MovieHeaderBox = new MovieHeaderBox(0, 2); movieBox.MovieHeaderBox.CreationTime = 0; movieBox.MovieHeaderBox.ModificationTime = 0; movieBox.MovieHeaderBox.Duration = 0; movieBox.MovieHeaderBox.Timescale = 1000; movieBox.MovieHeaderBox.NextTrackID = 99; movieBox.TrackBox = new TrackBox(); movieBox.TrackBox.TrackHeaderBox = new TrackHeaderBox(0, 3); movieBox.TrackBox.TrackHeaderBox.CreationTime = 0; movieBox.TrackBox.TrackHeaderBox.ModificationTime = 0; movieBox.TrackBox.TrackHeaderBox.TrackID = TrackID; movieBox.TrackBox.TrackHeaderBox.Duration = 0; movieBox.TrackBox.TrackHeaderBox.TrackIsAudio = false; movieBox.TrackBox.TrackHeaderBox.Width = (uint)spsInfo.width; movieBox.TrackBox.TrackHeaderBox.Height = (uint)spsInfo.height; movieBox.TrackBox.MediaBox = new MediaBox(); movieBox.TrackBox.MediaBox.MediaHeaderBox = new MediaHeaderBox(); movieBox.TrackBox.MediaBox.MediaHeaderBox.CreationTime = 0; movieBox.TrackBox.MediaBox.MediaHeaderBox.ModificationTime = 0; //movieBox.TrackBox.MediaBox.MediaHeaderBox.Timescale = 1200000; movieBox.TrackBox.MediaBox.MediaHeaderBox.Timescale = 1000; movieBox.TrackBox.MediaBox.MediaHeaderBox.Duration = 0; movieBox.TrackBox.MediaBox.HandlerBox = new HandlerBox(); movieBox.TrackBox.MediaBox.HandlerBox.HandlerType = HandlerType.vide; movieBox.TrackBox.MediaBox.HandlerBox.Name = "VideoHandler"; movieBox.TrackBox.MediaBox.MediaInformationBox = new MediaInformationBox(); movieBox.TrackBox.MediaBox.MediaInformationBox.VideoMediaHeaderBox = new VideoMediaHeaderBox(); movieBox.TrackBox.MediaBox.MediaInformationBox.DataInformationBox = new DataInformationBox(); movieBox.TrackBox.MediaBox.MediaInformationBox.DataInformationBox.DataReferenceBox = new DataReferenceBox(); movieBox.TrackBox.MediaBox.MediaInformationBox.DataInformationBox.DataReferenceBox.DataEntryBoxes = new List(); movieBox.TrackBox.MediaBox.MediaInformationBox.DataInformationBox.DataReferenceBox.DataEntryBoxes.Add(new DataEntryUrlBox(1)); movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox = new SampleTableBox(); movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleDescriptionBox = new SampleDescriptionBox(); movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleDescriptionBox.SampleEntries = new List(); AVC1SampleEntry avc1 = new AVC1SampleEntry(); avc1.AVCConfigurationBox = new AVCConfigurationBox(); //h264 avc1.Width = (ushort)movieBox.TrackBox.TrackHeaderBox.Width; avc1.Height = (ushort)movieBox.TrackBox.TrackHeaderBox.Height; avc1.AVCConfigurationBox.AVCLevelIndication = spsInfo.levelIdc; avc1.AVCConfigurationBox.AVCProfileIndication = spsInfo.profileIdc; avc1.AVCConfigurationBox.ProfileCompatibility = (byte)spsInfo.profileCompat; avc1.AVCConfigurationBox.PPSs = new List() { pps.RawData }; avc1.AVCConfigurationBox.SPSs = new List() { sps.RawData }; movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleDescriptionBox.SampleEntries.Add(avc1); movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.TimeToSampleBox = new TimeToSampleBox(); movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SyncSampleBox = new SyncSampleBox(); movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleToChunkBox = new SampleToChunkBox(); movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleSizeBox = new SampleSizeBox(); movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.ChunkOffsetBox = new ChunkOffsetBox(); movieBox.MovieExtendsBox = new MovieExtendsBox(); movieBox.MovieExtendsBox.TrackExtendsBoxs = new List(); TrackExtendsBox trex = new TrackExtendsBox(); trex.TrackID = TrackID; trex.DefaultSampleDescriptionIndex = SampleDescriptionIndex; trex.DefaultSampleDuration = 0; trex.DefaultSampleSize = 0; trex.DefaultSampleFlags = 0; movieBox.MovieExtendsBox.TrackExtendsBoxs.Add(trex); movieBox.ToBuffer(ref writer); var data = writer.FlushAndGetArray(); return data; } finally { FMp4ArrayPool.Return(buffer); } } /// /// styp /// /// public byte[] StypBox() { byte[] buffer = FMp4ArrayPool.Rent(1024); FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); try { SegmentTypeBox stypTypeBox = new SegmentTypeBox(); stypTypeBox.MajorBrand = "isom"; stypTypeBox.MinorVersion = "\0\0\0\0"; stypTypeBox.CompatibleBrands.Add("isom"); stypTypeBox.CompatibleBrands.Add("mp42"); stypTypeBox.CompatibleBrands.Add("msdh"); stypTypeBox.CompatibleBrands.Add("msix"); stypTypeBox.CompatibleBrands.Add("iso5"); stypTypeBox.CompatibleBrands.Add("iso6"); stypTypeBox.ToBuffer(ref writer); var data = writer.FlushAndGetArray(); return data; } finally { FMp4ArrayPool.Return(buffer); } } /// /// 编码其他视频数据盒子 /// /// public byte[] OtherVideoBox(in List nalus) { byte[] buffer = FMp4ArrayPool.Rent(nalus.Sum(s => s.RawData.Length + s.StartCodePrefix.Length) + 4096); FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); try { var truns = new List(); List rawdatas = new List(); uint iSize = 0; ulong lastTimestamp = 0; string key = string.Empty; for (var i = 0; i < nalus.Count; i++) { var nalu = nalus[i]; if (string.IsNullOrEmpty(key)) { key = nalu.GetKey(); } rawdatas.Add(nalu.RawData); if (nalu.DataType == Protocol.Enums.JT1078DataType.视频I帧) { iSize += (uint)(nalu.RawData.Length + nalu.StartCodePrefix.Length); } else { if (iSize > 0) { truns.Add(new TrackRunBox.TrackRunInfo() { SampleDuration=40, SampleSize = iSize, }); iSize = 0; } truns.Add(new TrackRunBox.TrackRunInfo() { SampleDuration = 40, SampleSize = (uint)(nalu.RawData.Length + nalu.StartCodePrefix.Length), }); } if (i == (nalus.Count - 1)) { lastTimestamp = nalu.Timestamp; } } if (TrackInfos.TryGetValue(key, out TrackInfo trackInfo)) { if (trackInfo.SN == uint.MaxValue) { trackInfo.SN = 1; } trackInfo.SN++; } else { trackInfo = new TrackInfo { SN = 1, DTS = 0 }; TrackInfos.Add(key, trackInfo); } var movieFragmentBox = new MovieFragmentBox(); movieFragmentBox.MovieFragmentHeaderBox = new MovieFragmentHeaderBox(); movieFragmentBox.MovieFragmentHeaderBox.SequenceNumber = trackInfo.SN; movieFragmentBox.TrackFragmentBox = new TrackFragmentBox(); movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox = new TrackFragmentHeaderBox(TfhdFlags); movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.TrackID = TrackID; movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.SampleDescriptionIndex = SampleDescriptionIndex; movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleDuration = DefaultSampleDuration; movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleSize = truns[0].SampleSize; movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleFlags = DefaultSampleFlags; movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox = new TrackFragmentBaseMediaDecodeTimeBox(); movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox.BaseMediaDecodeTime = trackInfo.DTS; trackInfo.DTS += (ulong)(truns.Count * DefaultSampleDuration); TrackInfos[key] = trackInfo; //trun movieFragmentBox.TrackFragmentBox.TrackRunBox = new TrackRunBox(flags: TrunFlags); movieFragmentBox.TrackFragmentBox.TrackRunBox.FirstSampleFlags = FirstSampleFlags; movieFragmentBox.TrackFragmentBox.TrackRunBox.TrackRunInfos = truns; movieFragmentBox.ToBuffer(ref writer); var mediaDataBox = new MediaDataBox(); mediaDataBox.Data = rawdatas; mediaDataBox.ToBuffer(ref writer); var data = writer.FlushAndGetArray(); return data; } finally { FMp4ArrayPool.Return(buffer); } } struct TrackInfo { public uint SN { get; set; } public ulong DTS { get; set; } } } }