@@ -10,4 +10,6 @@ ffmpeg -i ipc.264 -vcodec copy -f mp4 -movflags frag_keyframe+empty_moov ipc_fra | |||||
ffmpeg -i jt1078_3.h264 -c:v copy -f mp4 -movflags empty_moov+default_base_moof+frag_keyframe fragmented_base_moof_demo.mp4 | ffmpeg -i jt1078_3.h264 -c:v copy -f mp4 -movflags empty_moov+default_base_moof+frag_keyframe fragmented_base_moof_demo.mp4 | ||||
ffprobe -of json -show_frames jt1078_3.h264 > jt1078_3.json | |||||
chrome://media-internals/ | chrome://media-internals/ |
@@ -456,6 +456,7 @@ namespace JT1078.FMp4.Test | |||||
NalUnitType.PPS, | NalUnitType.PPS, | ||||
NalUnitType.AUD}; | NalUnitType.AUD}; | ||||
int i = 0; | int i = 0; | ||||
List<H264NALU> h264NALUs = new List<H264NALU>(); | |||||
foreach (var package in packages) | foreach (var package in packages) | ||||
{ | { | ||||
var otherStypBuffer = fMp4Encoder.EncoderStypBox(); | var otherStypBuffer = fMp4Encoder.EncoderStypBox(); | ||||
@@ -467,14 +468,6 @@ namespace JT1078.FMp4.Test | |||||
// continue; | // continue; | ||||
//} | //} | ||||
//int length = filterOtherNalus.Sum(s => s.RawData.Length); | //int length = filterOtherNalus.Sum(s => s.RawData.Length); | ||||
foreach(var nalu in otherNalus) | |||||
{ | |||||
//H264 NALU slice first_mb_in_slice | |||||
if ((nalu.RawData[1] & 0x80) == 0x80) | |||||
{ | |||||
} | |||||
} | |||||
var flag = package.Label3.DataType == Protocol.Enums.JT1078DataType.视频I帧 ? 1u : 0u; | var flag = package.Label3.DataType == Protocol.Enums.JT1078DataType.视频I帧 ? 1u : 0u; | ||||
var otherMoofBuffer = fMp4Encoder.EncoderMoofBox(otherNalus, package.Bodies.Length, package.Timestamp, package.LastFrameInterval, package.LastIFrameInterval, flag); | var otherMoofBuffer = fMp4Encoder.EncoderMoofBox(otherNalus, package.Bodies.Length, package.Timestamp, package.LastFrameInterval, package.LastIFrameInterval, flag); | ||||
var otherMdatBuffer = fMp4Encoder.EncoderMdatBox(otherNalus, package.Bodies.Length); | var otherMdatBuffer = fMp4Encoder.EncoderMdatBox(otherNalus, package.Bodies.Length); | ||||
@@ -488,6 +481,73 @@ namespace JT1078.FMp4.Test | |||||
fileStream.Close(); | fileStream.Close(); | ||||
} | } | ||||
[Fact] | |||||
public void Test5() | |||||
{ | |||||
FMp4Encoder fMp4Encoder = new FMp4Encoder(); | |||||
H264Decoder h264Decoder = new H264Decoder(); | |||||
var packages = ParseNALUTests(); | |||||
var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_5.mp4"); | |||||
if (File.Exists(filepath)) | |||||
{ | |||||
File.Delete(filepath); | |||||
} | |||||
using var fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | |||||
var ftyp = fMp4Encoder.EncoderFtypBox(); | |||||
fileStream.Write(ftyp); | |||||
List<NalUnitType> filter = new List<NalUnitType>() { | |||||
NalUnitType.SPS, | |||||
NalUnitType.PPS, | |||||
NalUnitType.AUD | |||||
}; | |||||
var iNalus=h264Decoder.ParseNALU(packages[0]); | |||||
//判断第一帧是否关键帧 | |||||
var moov = fMp4Encoder.EncoderMoovBox( | |||||
iNalus.FirstOrDefault(f=>f.NALUHeader.NalUnitType== NalUnitType.SPS), | |||||
iNalus.FirstOrDefault(f => f.NALUHeader.NalUnitType == NalUnitType.PPS)); | |||||
fileStream.Write(moov); | |||||
List<H264NALU> nalus = new List<H264NALU>(); | |||||
foreach (var package in packages) | |||||
{ | |||||
List<H264NALU> h264NALUs = h264Decoder.ParseNALU(package); | |||||
foreach (var nalu in h264NALUs) | |||||
{ | |||||
if (nalu.Slice) | |||||
{ | |||||
//H264 NALU slice first_mb_in_slice | |||||
nalus.Add(nalu); | |||||
} | |||||
else | |||||
{ | |||||
if (nalus.Count > 0) | |||||
{ | |||||
var iStypBuffer = fMp4Encoder.EncoderStypBox(); | |||||
fileStream.Write(iStypBuffer); | |||||
var firstNalu = nalus[0]; | |||||
var flag = firstNalu.DataType == Protocol.Enums.JT1078DataType.视频I帧 ? 1u : 0u; | |||||
int iSize = nalus.Where(w => w.DataType == Protocol.Enums.JT1078DataType.视频I帧) | |||||
.Sum(s => s.RawData.Length + s.StartCodePrefix.Length); | |||||
List<int> sizes = new List<int>(); | |||||
sizes.Add(iSize); | |||||
sizes = sizes.Concat(nalus.Where(w => w.DataType != Protocol.Enums.JT1078DataType.视频I帧) | |||||
.Select(s => s.RawData.Length + s.StartCodePrefix.Length).ToList()) | |||||
.ToList(); | |||||
var iMoofBuffer = fMp4Encoder.EncoderMoofBox(sizes, firstNalu.Timestamp, firstNalu.LastFrameInterval, firstNalu.LastIFrameInterval, flag); | |||||
var iMdatBuffer = fMp4Encoder.EncoderMdatBox(nalus.Select(s => s.RawData).ToList()); | |||||
var iSidxBuffer = fMp4Encoder.EncoderSidxBox(iMoofBuffer.Length + iMdatBuffer.Length, firstNalu.Timestamp, firstNalu.LastIFrameInterval, firstNalu.LastFrameInterval); | |||||
fileStream.Write(iSidxBuffer); | |||||
fileStream.Write(iMoofBuffer); | |||||
fileStream.Write(iMdatBuffer); | |||||
nalus.Clear(); | |||||
} | |||||
nalus.Add(nalu); | |||||
} | |||||
} | |||||
} | |||||
fileStream.Close(); | |||||
} | |||||
[Fact] | [Fact] | ||||
public void tkhd_width_height_test() | public void tkhd_width_height_test() | ||||
{ | { | ||||
@@ -38,6 +38,12 @@ namespace JT1078.FMp4 | |||||
TrackFragmentBox.ToBuffer(ref writer); | TrackFragmentBox.ToBuffer(ref writer); | ||||
} | } | ||||
End(ref writer); | End(ref writer); | ||||
var moofOffsetPosition = writer.GetMoofOffsetPosition(); | |||||
if (moofOffsetPosition > 0) | |||||
{ | |||||
writer.WriteUInt64Return((ulong)writer.GetCurrentPosition(), moofOffsetPosition); | |||||
} | |||||
writer.ClearMoofOffsetPosition(); | |||||
var trunOffsetPosition = writer.GetTrunOffsetPosition(); | var trunOffsetPosition = writer.GetTrunOffsetPosition(); | ||||
if (trunOffsetPosition > 0) | if (trunOffsetPosition > 0) | ||||
{ | { | ||||
@@ -38,7 +38,7 @@ namespace JT1078.FMp4 | |||||
MovieFragmentRandomAccessOffsetBox.ToBuffer(ref writer); | MovieFragmentRandomAccessOffsetBox.ToBuffer(ref writer); | ||||
} | } | ||||
End(ref writer); | End(ref writer); | ||||
var mfraSizePosition = writer.GetMfraSizePositionn(); | |||||
var mfraSizePosition = writer.GetMfraSizePosition(); | |||||
if (mfraSizePosition > 0) | if (mfraSizePosition > 0) | ||||
{ | { | ||||
writer.WriteInt32Return(writer.GetCurrentPosition() - SizePosition, mfraSizePosition); | writer.WriteInt32Return(writer.GetCurrentPosition() - SizePosition, mfraSizePosition); | ||||
@@ -60,7 +60,16 @@ namespace JT1078.FMp4 | |||||
writer.WriteUInt32(TrackID); | writer.WriteUInt32(TrackID); | ||||
if ((FMp4Constants.TFHD_FLAG_BASE_DATA_OFFSET & Flags) > 0) | if ((FMp4Constants.TFHD_FLAG_BASE_DATA_OFFSET & Flags) > 0) | ||||
{ | { | ||||
writer.WriteUInt64(BaseDataOffset); | |||||
if (BaseDataOffset > 0) | |||||
{ | |||||
writer.WriteUInt64(BaseDataOffset); | |||||
} | |||||
else | |||||
{ | |||||
//程序自动计算 | |||||
writer.CreateMoofOffsetPosition(); | |||||
writer.Skip(8, out _); | |||||
} | |||||
} | } | ||||
if ((FMp4Constants.TFHD_FLAG_SAMPLE_DESCRIPTION_INDEX & Flags) > 0) | if ((FMp4Constants.TFHD_FLAG_SAMPLE_DESCRIPTION_INDEX & Flags) > 0) | ||||
{ | { | ||||
@@ -108,7 +108,9 @@ namespace JT1078.FMp4 | |||||
} | } | ||||
} | } | ||||
uint IframeIntervalCache = 259960; | |||||
ulong IframeIntervalCache = 259960; | |||||
ulong cts = 0; | |||||
/// <summary> | /// <summary> | ||||
/// 编码sidx盒子 | /// 编码sidx盒子 | ||||
@@ -122,7 +124,9 @@ namespace JT1078.FMp4 | |||||
{ | { | ||||
SegmentIndexBox segmentIndexBox = new SegmentIndexBox(1); | SegmentIndexBox segmentIndexBox = new SegmentIndexBox(1); | ||||
segmentIndexBox.ReferenceID = 1; | segmentIndexBox.ReferenceID = 1; | ||||
segmentIndexBox.EarliestPresentationTime = timestamp; | |||||
cts = cts == 0 ? 2160000 : (cts + cts); | |||||
segmentIndexBox.EarliestPresentationTime = cts; | |||||
IframeIntervalCache += frameInterval; | |||||
segmentIndexBox.SegmentIndexs = new List<SegmentIndexBox.SegmentIndex>() | segmentIndexBox.SegmentIndexs = new List<SegmentIndexBox.SegmentIndex>() | ||||
{ | { | ||||
new SegmentIndexBox.SegmentIndex | new SegmentIndexBox.SegmentIndex | ||||
@@ -228,6 +232,88 @@ namespace JT1078.FMp4 | |||||
} | } | ||||
} | } | ||||
/// <summary> | |||||
/// 编码moov盒子 | |||||
/// </summary> | |||||
/// <returns></returns> | |||||
public byte[] EncoderMoovBox(in H264NALU sps, in H264NALU pps) | |||||
{ | |||||
byte[] buffer = FMp4ArrayPool.Rent(sps.RawData.Length+ pps.RawData.Length + 4096); | |||||
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 = 1; | |||||
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.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<DataEntryBox>(); | |||||
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<SampleEntry>(); | |||||
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<byte[]>() { pps.RawData }; | |||||
avc1.AVCConfigurationBox.SPSs = new List<byte[]>() { 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>(); | |||||
TrackExtendsBox trex = new TrackExtendsBox(); | |||||
trex.TrackID = 1; | |||||
trex.DefaultSampleDescriptionIndex = 1; | |||||
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); | |||||
} | |||||
} | |||||
/// <summary> | /// <summary> | ||||
/// 编码Moof盒子 | /// 编码Moof盒子 | ||||
/// </summary> | /// </summary> | ||||
@@ -283,6 +369,67 @@ namespace JT1078.FMp4 | |||||
} | } | ||||
} | } | ||||
/// <summary> | |||||
/// 编码Moof盒子 | |||||
/// </summary> | |||||
/// <returns></returns> | |||||
public byte[] EncoderMoofBox(List<int> naluSzies, ulong timestamp, uint frameInterval, uint IframeInterval, uint keyframeFlag) | |||||
{ | |||||
byte[] buffer = FMp4ArrayPool.Rent(4096); | |||||
FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); | |||||
try | |||||
{ | |||||
var movieFragmentBox = new MovieFragmentBox(); | |||||
movieFragmentBox.MovieFragmentHeaderBox = new MovieFragmentHeaderBox(); | |||||
movieFragmentBox.MovieFragmentHeaderBox.SequenceNumber = sn++; | |||||
movieFragmentBox.TrackFragmentBox = new TrackFragmentBox(); | |||||
//0x39 写文件 | |||||
//0x02 分段 | |||||
//0x2003a | |||||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox = new TrackFragmentHeaderBox(0x2003a); | |||||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.TrackID = 1; | |||||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.SampleDescriptionIndex = 1; | |||||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleDuration = frameInterval; | |||||
//movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleDuration = 48000; | |||||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleSize = (uint)naluSzies[0]; | |||||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleFlags = 0x1010000; | |||||
movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox = new TrackFragmentBaseMediaDecodeTimeBox(); | |||||
movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox.BaseMediaDecodeTime = cts; | |||||
//trun | |||||
//0x39 写文件 | |||||
//0x02 分段 | |||||
//0x205 | |||||
//uint flag = 0x000200 | 0x000800 | 0x000400 | 0x000100; | |||||
uint flag = 0x0001; | |||||
if (!first) | |||||
{ | |||||
flag |= 0x0004; | |||||
first = true; | |||||
} | |||||
flag |= 0x000200; | |||||
movieFragmentBox.TrackFragmentBox.TrackRunBox = new TrackRunBox(flags: flag); | |||||
movieFragmentBox.TrackFragmentBox.TrackRunBox.FirstSampleFlags = 33554432; | |||||
movieFragmentBox.TrackFragmentBox.TrackRunBox.TrackRunInfos = new List<TrackRunBox.TrackRunInfo>(); | |||||
foreach(var size in naluSzies) | |||||
{ | |||||
movieFragmentBox.TrackFragmentBox.TrackRunBox.TrackRunInfos.Add(new TrackRunBox.TrackRunInfo() | |||||
{ | |||||
//SampleDuration= frameInterval, | |||||
SampleSize = (uint)size, | |||||
//SampleCompositionTimeOffset = frameInterval, | |||||
//SampleFlags = movieFragmentBox.TrackFragmentBox.TrackRunBox.Flags | |||||
}); | |||||
} | |||||
movieFragmentBox.ToBuffer(ref writer); | |||||
var data = writer.FlushAndGetArray(); | |||||
return data; | |||||
} | |||||
finally | |||||
{ | |||||
FMp4ArrayPool.Return(buffer); | |||||
} | |||||
} | |||||
/// <summary> | /// <summary> | ||||
/// 编码Mdat盒子 | /// 编码Mdat盒子 | ||||
/// </summary> | /// </summary> | ||||
@@ -305,6 +452,28 @@ namespace JT1078.FMp4 | |||||
} | } | ||||
} | } | ||||
/// <summary> | |||||
/// 编码Mdat盒子 | |||||
/// </summary> | |||||
/// <returns></returns> | |||||
public byte[] EncoderMdatBox(List<byte[]> nalus) | |||||
{ | |||||
byte[] buffer = FMp4ArrayPool.Rent(nalus.Sum(s=>s.Length) + 4096); | |||||
FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); | |||||
try | |||||
{ | |||||
var mediaDataBox = new MediaDataBox(); | |||||
mediaDataBox.Data = nalus; | |||||
mediaDataBox.ToBuffer(ref writer); | |||||
var data = writer.FlushAndGetArray(); | |||||
return data; | |||||
} | |||||
finally | |||||
{ | |||||
FMp4ArrayPool.Return(buffer); | |||||
} | |||||
} | |||||
/// <summary> | /// <summary> | ||||
/// 编码首个视频盒子 | /// 编码首个视频盒子 | ||||
/// </summary> | /// </summary> | ||||
@@ -1348,18 +1348,36 @@ | |||||
</summary> | </summary> | ||||
<returns></returns> | <returns></returns> | ||||
</member> | </member> | ||||
<member name="M:JT1078.FMp4.FMp4Encoder.EncoderMoovBox(JT1078.Protocol.H264.H264NALU@,JT1078.Protocol.H264.H264NALU@)"> | |||||
<summary> | |||||
编码moov盒子 | |||||
</summary> | |||||
<returns></returns> | |||||
</member> | |||||
<member name="M:JT1078.FMp4.FMp4Encoder.EncoderMoofBox(System.Collections.Generic.List{JT1078.Protocol.H264.H264NALU},System.Int32,System.UInt64,System.UInt32,System.UInt32,System.UInt32)"> | <member name="M:JT1078.FMp4.FMp4Encoder.EncoderMoofBox(System.Collections.Generic.List{JT1078.Protocol.H264.H264NALU},System.Int32,System.UInt64,System.UInt32,System.UInt32,System.UInt32)"> | ||||
<summary> | <summary> | ||||
编码Moof盒子 | 编码Moof盒子 | ||||
</summary> | </summary> | ||||
<returns></returns> | <returns></returns> | ||||
</member> | </member> | ||||
<member name="M:JT1078.FMp4.FMp4Encoder.EncoderMoofBox(System.Collections.Generic.List{System.Int32},System.UInt64,System.UInt32,System.UInt32,System.UInt32)"> | |||||
<summary> | |||||
编码Moof盒子 | |||||
</summary> | |||||
<returns></returns> | |||||
</member> | |||||
<member name="M:JT1078.FMp4.FMp4Encoder.EncoderMdatBox(System.Collections.Generic.List{JT1078.Protocol.H264.H264NALU},System.Int32)"> | <member name="M:JT1078.FMp4.FMp4Encoder.EncoderMdatBox(System.Collections.Generic.List{JT1078.Protocol.H264.H264NALU},System.Int32)"> | ||||
<summary> | <summary> | ||||
编码Mdat盒子 | 编码Mdat盒子 | ||||
</summary> | </summary> | ||||
<returns></returns> | <returns></returns> | ||||
</member> | </member> | ||||
<member name="M:JT1078.FMp4.FMp4Encoder.EncoderMdatBox(System.Collections.Generic.List{System.Byte[]})"> | |||||
<summary> | |||||
编码Mdat盒子 | |||||
</summary> | |||||
<returns></returns> | |||||
</member> | |||||
<member name="M:JT1078.FMp4.FMp4Encoder.EncoderFirstVideoBox(JT1078.Protocol.JT1078Package)"> | <member name="M:JT1078.FMp4.FMp4Encoder.EncoderFirstVideoBox(JT1078.Protocol.JT1078Package)"> | ||||
<summary> | <summary> | ||||
编码首个视频盒子 | 编码首个视频盒子 | ||||
@@ -13,12 +13,14 @@ namespace JT1078.FMp4.MessagePack | |||||
private int TrunDataOffsetPosition; | private int TrunDataOffsetPosition; | ||||
private int MfraSizePosition; | private int MfraSizePosition; | ||||
private int MoofOffsetPosition; | |||||
public FMp4MessagePackWriter(Span<byte> buffer) | public FMp4MessagePackWriter(Span<byte> buffer) | ||||
{ | { | ||||
this.writer = new FMp4BufferWriter(buffer); | this.writer = new FMp4BufferWriter(buffer); | ||||
TrunDataOffsetPosition = 0; | TrunDataOffsetPosition = 0; | ||||
MfraSizePosition = 0; | MfraSizePosition = 0; | ||||
MoofOffsetPosition = 0; | |||||
} | } | ||||
public byte[] FlushAndGetArray() | public byte[] FlushAndGetArray() | ||||
{ | { | ||||
@@ -109,6 +111,10 @@ namespace JT1078.FMp4.MessagePack | |||||
{ | { | ||||
BinaryPrimitives.WriteUInt32BigEndian(writer.Written.Slice(position, 4), value); | BinaryPrimitives.WriteUInt32BigEndian(writer.Written.Slice(position, 4), value); | ||||
} | } | ||||
public void WriteUInt64Return(ulong value, int position) | |||||
{ | |||||
BinaryPrimitives.WriteUInt64BigEndian(writer.Written.Slice(position, 8), value); | |||||
} | |||||
public void WriteByteReturn(byte value, int position) | public void WriteByteReturn(byte value, int position) | ||||
{ | { | ||||
writer.Written[position] = value; | writer.Written[position] = value; | ||||
@@ -139,7 +145,7 @@ namespace JT1078.FMp4.MessagePack | |||||
MfraSizePosition = writer.WrittenCount; | MfraSizePosition = writer.WrittenCount; | ||||
} | } | ||||
public int GetMfraSizePositionn() | |||||
public int GetMfraSizePosition() | |||||
{ | { | ||||
return MfraSizePosition; | return MfraSizePosition; | ||||
} | } | ||||
@@ -149,6 +155,22 @@ namespace JT1078.FMp4.MessagePack | |||||
MfraSizePosition = 0; | MfraSizePosition = 0; | ||||
} | } | ||||
public void CreateMoofOffsetPosition() | |||||
{ | |||||
MoofOffsetPosition = writer.WrittenCount; | |||||
} | |||||
public int GetMoofOffsetPosition() | |||||
{ | |||||
return MoofOffsetPosition; | |||||
} | |||||
public void ClearMoofOffsetPosition() | |||||
{ | |||||
MoofOffsetPosition = 0; | |||||
} | |||||
/// <summary> | /// <summary> | ||||
/// ref | /// ref | ||||
/// </summary> | /// </summary> | ||||
@@ -336,13 +336,13 @@ namespace JT1078.Protocol.Test | |||||
[Fact] | [Fact] | ||||
public void MergeTest() | public void MergeTest() | ||||
{ | { | ||||
var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "JT1078.txt")); | |||||
var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "h264","JT1078_1.txt")); | |||||
JT1078Package merge=null; | JT1078Package merge=null; | ||||
int mergeBodyLength=0; | int mergeBodyLength=0; | ||||
foreach (var line in lines) | foreach (var line in lines) | ||||
{ | { | ||||
var data = line.Split(','); | var data = line.Split(','); | ||||
var bytes = data[5].ToHexBytes(); | |||||
var bytes = data[6].ToHexBytes(); | |||||
JT1078Package package = JT1078Serializer.Deserialize(bytes); | JT1078Package package = JT1078Serializer.Deserialize(bytes); | ||||
mergeBodyLength += package.DataBodyLength; | mergeBodyLength += package.DataBodyLength; | ||||
merge = JT1078Serializer.Merge(package); | merge = JT1078Serializer.Merge(package); | ||||
@@ -18,7 +18,7 @@ namespace JT1078.Protocol.H264 | |||||
public List<H264NALU> ParseNALU(JT1078Package package, string key = null) | public List<H264NALU> ParseNALU(JT1078Package package, string key = null) | ||||
{ | { | ||||
List<H264NALU> h264NALUs = new List<H264NALU>(); | List<H264NALU> h264NALUs = new List<H264NALU>(); | ||||
int i=0,state=0; | |||||
int i=0,state=0,laststate=0; | |||||
int? lastIndex=null; | int? lastIndex=null; | ||||
int length = package.Bodies.Length; | int length = package.Bodies.Length; | ||||
byte value; | byte value; | ||||
@@ -45,9 +45,10 @@ namespace JT1078.Protocol.H264 | |||||
if (lastIndex.HasValue) | if (lastIndex.HasValue) | ||||
{ | { | ||||
var tmp = buffer.Slice(lastIndex.Value, i - state - 1 - lastIndex.Value); | var tmp = buffer.Slice(lastIndex.Value, i - state - 1 - lastIndex.Value); | ||||
h264NALUs.Add(Create(package, tmp, state)); | |||||
h264NALUs.Add(Create(package, tmp, state+1)); | |||||
} | } | ||||
lastIndex = i; | lastIndex = i; | ||||
laststate = state+1; | |||||
state = 0; | state = 0; | ||||
} | } | ||||
else | else | ||||
@@ -61,11 +62,69 @@ namespace JT1078.Protocol.H264 | |||||
} | } | ||||
if (lastIndex.HasValue) | if (lastIndex.HasValue) | ||||
{ | { | ||||
h264NALUs.Add(Create(package, buffer.Slice(lastIndex.Value), 4)); | |||||
h264NALUs.Add(Create(package, buffer.Slice(lastIndex.Value), laststate)); | |||||
} | } | ||||
return h264NALUs; | return h264NALUs; | ||||
} | } | ||||
/// <summary> | |||||
/// | |||||
/// <see cref="https://github.com/samirkumardas/jmuxer/blob/master/src/parsers/h264.js"/> | |||||
/// </summary> | |||||
/// <param name="package"></param> | |||||
/// <param name="h264NALUs"></param> | |||||
/// <param name="key"></param> | |||||
/// <returns></returns> | |||||
public void ParseNALU(JT1078Package package, List<H264NALU> h264NALUs,string key = null) | |||||
{ | |||||
int i = 0, state = 0, laststate = 0; | |||||
int? lastIndex = null; | |||||
int length = package.Bodies.Length; | |||||
byte value; | |||||
ReadOnlySpan<byte> buffer = package.Bodies; | |||||
while (i < length) | |||||
{ | |||||
value = buffer[i++]; | |||||
switch (state) | |||||
{ | |||||
case 0: | |||||
if (value == 0) state = 1; | |||||
break; | |||||
case 1: | |||||
state = value == 0 ? 2 : 0; | |||||
break; | |||||
case 2: | |||||
case 3: | |||||
if (value == 0) | |||||
{ | |||||
state = 3; | |||||
} | |||||
else if (value == 1 && i < length) | |||||
{ | |||||
if (lastIndex.HasValue) | |||||
{ | |||||
var tmp = buffer.Slice(lastIndex.Value, i - state - 1 - lastIndex.Value); | |||||
h264NALUs.Add(Create(package, tmp, lastIndex.Value)); | |||||
} | |||||
lastIndex = i; | |||||
laststate = state + 1; | |||||
state = 0; | |||||
} | |||||
else | |||||
{ | |||||
state = 0; | |||||
} | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
if (lastIndex.HasValue) | |||||
{ | |||||
h264NALUs.Add(Create(package, buffer.Slice(lastIndex.Value), laststate)); | |||||
} | |||||
} | |||||
private H264NALU Create(JT1078Package package,ReadOnlySpan<byte> nalu, int startCodePrefix) | private H264NALU Create(JT1078Package package,ReadOnlySpan<byte> nalu, int startCodePrefix) | ||||
{ | { | ||||
H264NALU nALU = new H264NALU(); | H264NALU nALU = new H264NALU(); | ||||
@@ -75,6 +134,7 @@ namespace JT1078.Protocol.H264 | |||||
nALU.LastFrameInterval = package.LastFrameInterval; | nALU.LastFrameInterval = package.LastFrameInterval; | ||||
nALU.LastIFrameInterval = package.LastIFrameInterval; | nALU.LastIFrameInterval = package.LastIFrameInterval; | ||||
nALU.Timestamp = package.Timestamp; | nALU.Timestamp = package.Timestamp; | ||||
nALU.Slice = (nalu[1] & 0x80)== 0x80; | |||||
nALU.RawData = nalu.ToArray(); | nALU.RawData = nalu.ToArray(); | ||||
if (startCodePrefix == 3) | if (startCodePrefix == 3) | ||||
{ | { | ||||
@@ -40,6 +40,11 @@ namespace JT1078.Protocol.H264 | |||||
/// 当数据类型为01000时,则没有该字段 | /// 当数据类型为01000时,则没有该字段 | ||||
/// </summary> | /// </summary> | ||||
public ulong Timestamp { get; set; } | public ulong Timestamp { get; set; } | ||||
/// <summary> | |||||
/// 是否切片 0x80 | |||||
/// </summary> | |||||
public bool Slice { get; set; } | |||||
/// <summary> | /// <summary> | ||||
/// 数据体 | /// 数据体 | ||||
/// </summary> | /// </summary> | ||||
@@ -122,6 +122,16 @@ | |||||
<param name="key"></param> | <param name="key"></param> | ||||
<returns></returns> | <returns></returns> | ||||
</member> | </member> | ||||
<member name="M:JT1078.Protocol.H264.H264Decoder.ParseNALU(JT1078.Protocol.JT1078Package,System.Collections.Generic.List{JT1078.Protocol.H264.H264NALU},System.String)"> | |||||
<summary> | |||||
<see cref="!:https://github.com/samirkumardas/jmuxer/blob/master/src/parsers/h264.js"/> | |||||
</summary> | |||||
<param name="package"></param> | |||||
<param name="h264NALUs"></param> | |||||
<param name="key"></param> | |||||
<returns></returns> | |||||
</member> | |||||
<member name="M:JT1078.Protocol.H264.H264Decoder.DiscardEmulationPreventionBytes(System.ReadOnlySpan{System.Byte})"> | <member name="M:JT1078.Protocol.H264.H264Decoder.DiscardEmulationPreventionBytes(System.ReadOnlySpan{System.Byte})"> | ||||
<summary> | <summary> | ||||
Expunge any "Emulation Prevention" bytes from a "Raw Byte Sequence Payload" | Expunge any "Emulation Prevention" bytes from a "Raw Byte Sequence Payload" | ||||
@@ -166,6 +176,11 @@ | |||||
当数据类型为01000时,则没有该字段 | 当数据类型为01000时,则没有该字段 | ||||
</summary> | </summary> | ||||
</member> | </member> | ||||
<member name="P:JT1078.Protocol.H264.H264NALU.Slice"> | |||||
<summary> | |||||
是否切片 0x80 | |||||
</summary> | |||||
</member> | |||||
<member name="P:JT1078.Protocol.H264.H264NALU.RawData"> | <member name="P:JT1078.Protocol.H264.H264NALU.RawData"> | ||||
<summary> | <summary> | ||||
数据体 | 数据体 | ||||