@@ -89,8 +89,8 @@ var hex = JT1078Serializer.Serialize(jT1078Package).ToHexString(); | |||||
2.拆解: | 2.拆解: | ||||
30 31 63 64 --帧头表示 | 30 31 63 64 --帧头表示 | ||||
81 --Label1 =>10000001 V P X CC | |||||
E2 --Label2 =>11100010 M PT | |||||
81 --Label1 =>10000001 V P X CC | |||||
E2 --Label2 =>11100010 M PT | |||||
10 88 --SN 包序号 | 10 88 --SN 包序号 | ||||
01 12 34 56 78 10 --SIM卡号 | 01 12 34 56 78 10 --SIM卡号 | ||||
01 --逻辑通道号 | 01 --逻辑通道号 | ||||
@@ -8,7 +8,7 @@ | |||||
<PackageProjectUrl>https://github.com/SmallChi/JT1078</PackageProjectUrl> | <PackageProjectUrl>https://github.com/SmallChi/JT1078</PackageProjectUrl> | ||||
<licenseUrl>https://github.com/SmallChi/JT1078/blob/master/LICENSE</licenseUrl> | <licenseUrl>https://github.com/SmallChi/JT1078/blob/master/LICENSE</licenseUrl> | ||||
<license>https://github.com/SmallChi/JT1078/blob/master/LICENSE</license> | <license>https://github.com/SmallChi/JT1078/blob/master/LICENSE</license> | ||||
<Version>1.2.0-preview2</Version> | |||||
<Version>1.2.0-preview6</Version> | |||||
<PackageLicenseFile>LICENSE</PackageLicenseFile> | <PackageLicenseFile>LICENSE</PackageLicenseFile> | ||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> | <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> | ||||
<AnalysisLevel>latest</AnalysisLevel> | <AnalysisLevel>latest</AnalysisLevel> | ||||
@@ -8,7 +8,7 @@ | |||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" /> | <PackageReference Include="BenchmarkDotNet" Version="0.13.1" /> | ||||
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.1" /> | <PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.1" /> | ||||
<PackageReference Include="System.Buffers" Version="4.5.1" /> | <PackageReference Include="System.Buffers" Version="4.5.1" /> | ||||
<PackageReference Include="System.Memory" Version="4.5.4" /> | |||||
<PackageReference Include="System.Memory" Version="4.5.5" /> | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<ProjectReference Include="..\JT1078.Flv\JT1078.Flv.csproj" /> | <ProjectReference Include="..\JT1078.Flv\JT1078.Flv.csproj" /> | ||||
@@ -7,13 +7,13 @@ | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> | |||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" /> | |||||
<PackageReference Include="xunit" Version="2.4.1" /> | <PackageReference Include="xunit" Version="2.4.1" /> | ||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> | |||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> | |||||
<PrivateAssets>all</PrivateAssets> | <PrivateAssets>all</PrivateAssets> | ||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
</PackageReference> | </PackageReference> | ||||
<PackageReference Include="coverlet.collector" Version="3.1.0"> | |||||
<PackageReference Include="coverlet.collector" Version="3.1.2"> | |||||
<PrivateAssets>all</PrivateAssets> | <PrivateAssets>all</PrivateAssets> | ||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
</PackageReference> | </PackageReference> | ||||
@@ -12,6 +12,7 @@ using System.Buffers.Binary; | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.IO; | using System.IO; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Net.Sockets; | |||||
using System.Text; | using System.Text; | ||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
@@ -498,12 +499,15 @@ namespace JT1078.FMp4.Test | |||||
} | } | ||||
[Fact] | [Fact] | ||||
public void Test4_3() | |||||
public void Test4_4() | |||||
{ | { | ||||
uint a = uint.MaxValue; | |||||
var b = a + 1; | |||||
FMp4Encoder fMp4Encoder = new FMp4Encoder(); | FMp4Encoder fMp4Encoder = new FMp4Encoder(); | ||||
H264Decoder h264Decoder = new H264Decoder(); | H264Decoder h264Decoder = new H264Decoder(); | ||||
var packages = ParseNALUTests1(); | var packages = ParseNALUTests1(); | ||||
var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_7_4_3.mp4"); | |||||
var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_7_4_4.mp4"); | |||||
if (File.Exists(filepath)) | if (File.Exists(filepath)) | ||||
{ | { | ||||
File.Delete(filepath); | File.Delete(filepath); | ||||
@@ -520,9 +524,8 @@ namespace JT1078.FMp4.Test | |||||
iNalus.FirstOrDefault(f => f.NALUHeader.NalUnitType == NalUnitType.SPS), | iNalus.FirstOrDefault(f => f.NALUHeader.NalUnitType == NalUnitType.SPS), | ||||
iNalus.FirstOrDefault(f => f.NALUHeader.NalUnitType == NalUnitType.PPS)); | iNalus.FirstOrDefault(f => f.NALUHeader.NalUnitType == NalUnitType.PPS)); | ||||
fileStream.Write(moov); | fileStream.Write(moov); | ||||
List<JT1078Package> tmp = new List<JT1078Package>(); | |||||
List<H264NALU> stream = new List<H264NALU>(); | |||||
Queue<Mp4Frame> mp4Frames = new Queue<Mp4Frame>(); | |||||
List<NalUnitType> filter = new List<NalUnitType>(); | List<NalUnitType> filter = new List<NalUnitType>(); | ||||
filter.Add(NalUnitType.SEI); | filter.Add(NalUnitType.SEI); | ||||
filter.Add(NalUnitType.PPS); | filter.Add(NalUnitType.PPS); | ||||
@@ -531,53 +534,42 @@ namespace JT1078.FMp4.Test | |||||
foreach (var package in packages) | foreach (var package in packages) | ||||
{ | { | ||||
List<H264NALU> h264NALUs = h264Decoder.ParseNALU(package); | List<H264NALU> h264NALUs = h264Decoder.ParseNALU(package); | ||||
if (h264NALUs!=null && h264NALUs.Count>0) | |||||
if (h264NALUs != null && h264NALUs.Count > 0) | |||||
{ | { | ||||
stream.AddRange(h264NALUs.Where(w=> !filter.Contains(w.NALUHeader.NalUnitType))); | |||||
Mp4Frame mp4Frame = new Mp4Frame | |||||
{ | |||||
Key = package.GetKey(), | |||||
KeyFrame = package.Label3.DataType == JT1078DataType.视频I帧 | |||||
}; | |||||
mp4Frame.NALUs = h264NALUs; | |||||
mp4Frames.Enqueue(mp4Frame); | |||||
} | } | ||||
} | } | ||||
List<H264NALU> tmp1 = new List<H264NALU>(); | |||||
H264NALU prevNalu = null; | |||||
foreach (var item in stream) | |||||
while (mp4Frames.TryDequeue(out Mp4Frame frame)) | |||||
{ | { | ||||
if (item.NALUHeader.KeyFrame) | |||||
{ | |||||
if (tmp1.Count>0) | |||||
{ | |||||
fileStream.Write(fMp4Encoder.OtherVideoBox(tmp1)); | |||||
tmp1.Clear(); | |||||
} | |||||
tmp1.Add(item); | |||||
fileStream.Write(fMp4Encoder.OtherVideoBox(tmp1)); | |||||
tmp1.Clear(); | |||||
prevNalu=item; | |||||
continue; | |||||
} | |||||
if (prevNalu!=null) //第一帧I帧 | |||||
{ | |||||
if (tmp1.Count>1) | |||||
{ | |||||
fileStream.Write(fMp4Encoder.StypBox()); | |||||
fileStream.Write(fMp4Encoder.OtherVideoBox(tmp1)); | |||||
tmp1.Clear(); | |||||
} | |||||
tmp1.Add(item); | |||||
} | |||||
fileStream.Write(fMp4Encoder.OtherVideoBox(frame.NALUs, frame.Key, frame.KeyFrame)); | |||||
} | } | ||||
fileStream.Close(); | fileStream.Close(); | ||||
} | |||||
} | |||||
class Mp4Frame | |||||
{ | |||||
public string Key { get; set; } | |||||
public bool KeyFrame { get; set; } | |||||
public List<H264NALU> NALUs { get; set; } | |||||
} | |||||
[Fact] | [Fact] | ||||
public void WebSocketMp4() | public void WebSocketMp4() | ||||
{ | { | ||||
var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_8.mp4"); | |||||
var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_10.mp4"); | |||||
if (File.Exists(filepath)) | if (File.Exists(filepath)) | ||||
{ | { | ||||
File.Delete(filepath); | File.Delete(filepath); | ||||
} | } | ||||
using var fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | using var fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | ||||
System.Net.WebSockets.ClientWebSocket clientWebSocket = new System.Net.WebSockets.ClientWebSocket(); | System.Net.WebSockets.ClientWebSocket clientWebSocket = new System.Net.WebSockets.ClientWebSocket(); | ||||
clientWebSocket.ConnectAsync(new Uri("ws://127.0.0.1:81/live/JT1078_7.live.mp4"), CancellationToken.None).GetAwaiter().GetResult(); | |||||
clientWebSocket.ConnectAsync(new Uri("ws://127.0.0.1:8080/live/JT1078_7.live.mp4"), CancellationToken.None).GetAwaiter().GetResult(); | |||||
Task.Run(async() => | Task.Run(async() => | ||||
{ | { | ||||
while (true) | while (true) | ||||
@@ -16,26 +16,6 @@ namespace JT1078.FMp4 | |||||
/// FMp4编码 | /// FMp4编码 | ||||
/// fmp4 | /// fmp4 | ||||
/// stream data | /// stream data | ||||
/// ftyp | |||||
/// moov | |||||
/// mvex | |||||
/// trex Video | |||||
/// trex Audio | |||||
/// styp 1 | |||||
/// sidx 1 Video | |||||
/// sidx 1 Audio | |||||
/// moof 1 | |||||
/// traf 1 Video | |||||
/// traf 1 Audio | |||||
/// mdat 1 | |||||
/// ... | |||||
/// styp n | |||||
/// sidx 1 Video | |||||
/// sidx n Audio | |||||
/// moof n | |||||
/// traf n Video | |||||
/// traf n Audio | |||||
/// mdat n | |||||
/// ref: https://www.w3.org/TR/mse-byte-stream-format-isobmff/#movie-fragment-relative-addressing | /// ref: https://www.w3.org/TR/mse-byte-stream-format-isobmff/#movie-fragment-relative-addressing | ||||
/// </summary> | /// </summary> | ||||
public class FMp4Encoder | public class FMp4Encoder | ||||
@@ -49,7 +29,7 @@ namespace JT1078.FMp4 | |||||
FMp4Constants.TFHD_FLAG_DEFAULT_FLAGS; | FMp4Constants.TFHD_FLAG_DEFAULT_FLAGS; | ||||
const uint TrunFlags = FMp4Constants.TRUN_FLAG_DATA_OFFSET_PRESENT | | const uint TrunFlags = FMp4Constants.TRUN_FLAG_DATA_OFFSET_PRESENT | | ||||
FMp4Constants.TRUN_FLAG_SAMPLE_SIZE_PRESENT| | |||||
FMp4Constants.TRUN_FLAG_SAMPLE_SIZE_PRESENT | | |||||
FMp4Constants.TRUN_FLAG_FIRST_SAMPLE_FLAGS_PRESENT; | FMp4Constants.TRUN_FLAG_FIRST_SAMPLE_FLAGS_PRESENT; | ||||
const uint SampleDescriptionIndex = 1; | const uint SampleDescriptionIndex = 1; | ||||
@@ -325,10 +305,9 @@ namespace JT1078.FMp4 | |||||
/// 编码首帧 | /// 编码首帧 | ||||
/// ftyp moov | /// ftyp moov | ||||
/// </summary> | /// </summary> | ||||
/// <param name="sps"></param> | |||||
/// <param name="pps"></param> | |||||
/// <param name="avframe"></param> | |||||
/// <returns></returns> | /// <returns></returns> | ||||
public byte[] FirstVideoBox(in H264NALU sps, in H264NALU pps) | |||||
public byte[] FirstVideoBox(in JT1078AVFrame avframe) | |||||
{ | { | ||||
byte[] buffer = FMp4ArrayPool.Rent(CacheSize); | byte[] buffer = FMp4ArrayPool.Rent(CacheSize); | ||||
FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); | FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); | ||||
@@ -348,8 +327,6 @@ namespace JT1078.FMp4 | |||||
fileTypeBox.CompatibleBrands.Add("iso6"); | fileTypeBox.CompatibleBrands.Add("iso6"); | ||||
fileTypeBox.ToBuffer(ref writer); | fileTypeBox.ToBuffer(ref writer); | ||||
ExpGolombReader h264GolombReader = new ExpGolombReader(sps.RawData); | |||||
var spsInfo = h264GolombReader.ReadSPS(); | |||||
//moov | //moov | ||||
MovieBox movieBox = new MovieBox(); | MovieBox movieBox = new MovieBox(); | ||||
movieBox.MovieHeaderBox = new MovieHeaderBox(0, 2); | movieBox.MovieHeaderBox = new MovieHeaderBox(0, 2); | ||||
@@ -366,8 +343,8 @@ namespace JT1078.FMp4 | |||||
movieBox.TrackBox.TrackHeaderBox.TrackID = VideoTrackID; | movieBox.TrackBox.TrackHeaderBox.TrackID = VideoTrackID; | ||||
movieBox.TrackBox.TrackHeaderBox.Duration = 0; | movieBox.TrackBox.TrackHeaderBox.Duration = 0; | ||||
movieBox.TrackBox.TrackHeaderBox.TrackIsAudio = false; | movieBox.TrackBox.TrackHeaderBox.TrackIsAudio = false; | ||||
movieBox.TrackBox.TrackHeaderBox.Width = (uint)spsInfo.width; | |||||
movieBox.TrackBox.TrackHeaderBox.Height = (uint)spsInfo.height; | |||||
movieBox.TrackBox.TrackHeaderBox.Width = (uint)avframe.Width; | |||||
movieBox.TrackBox.TrackHeaderBox.Height = (uint)avframe.Height; | |||||
movieBox.TrackBox.MediaBox = new MediaBox(); | movieBox.TrackBox.MediaBox = new MediaBox(); | ||||
movieBox.TrackBox.MediaBox.MediaHeaderBox = new MediaHeaderBox(); | movieBox.TrackBox.MediaBox.MediaHeaderBox = new MediaHeaderBox(); | ||||
movieBox.TrackBox.MediaBox.MediaHeaderBox.CreationTime = 0; | movieBox.TrackBox.MediaBox.MediaHeaderBox.CreationTime = 0; | ||||
@@ -386,16 +363,19 @@ namespace JT1078.FMp4 | |||||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox = new SampleTableBox(); | movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox = new SampleTableBox(); | ||||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleDescriptionBox = new SampleDescriptionBox(); | movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleDescriptionBox = new SampleDescriptionBox(); | ||||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleDescriptionBox.SampleEntries = new List<SampleEntry>(); | movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleDescriptionBox.SampleEntries = new List<SampleEntry>(); | ||||
//h264 | |||||
//h264 | |||||
//profileIdc profileCompat levelIdc | |||||
//0x64 0x00 0x1e | |||||
//avc1.64001e | |||||
AVC1SampleEntry avc1 = new AVC1SampleEntry(); | AVC1SampleEntry avc1 = new AVC1SampleEntry(); | ||||
avc1.AVCConfigurationBox = new AVCConfigurationBox(); | avc1.AVCConfigurationBox = new AVCConfigurationBox(); | ||||
avc1.Width = (ushort)movieBox.TrackBox.TrackHeaderBox.Width; | avc1.Width = (ushort)movieBox.TrackBox.TrackHeaderBox.Width; | ||||
avc1.Height = (ushort)movieBox.TrackBox.TrackHeaderBox.Height; | 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 }; | |||||
avc1.AVCConfigurationBox.AVCLevelIndication = avframe.LevelIdc; | |||||
avc1.AVCConfigurationBox.AVCProfileIndication = avframe.ProfileIdc; | |||||
avc1.AVCConfigurationBox.ProfileCompatibility = avframe.ProfileCompat; | |||||
avc1.AVCConfigurationBox.PPSs = new List<byte[]>() { avframe.PPS.RawData }; | |||||
avc1.AVCConfigurationBox.SPSs = new List<byte[]>() { avframe.SPS.RawData }; | |||||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleDescriptionBox.SampleEntries.Add(avc1); | movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleDescriptionBox.SampleEntries.Add(avc1); | ||||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.TimeToSampleBox = new TimeToSampleBox(); | movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.TimeToSampleBox = new TimeToSampleBox(); | ||||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SyncSampleBox = new SyncSampleBox(); | movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SyncSampleBox = new SyncSampleBox(); | ||||
@@ -430,7 +410,7 @@ namespace JT1078.FMp4 | |||||
/// styp sidx moof mdat | /// styp sidx moof mdat | ||||
/// </summary> | /// </summary> | ||||
/// <returns></returns> | /// <returns></returns> | ||||
public byte[] OtherVideoBox(in List<H264NALU> nalus) | |||||
public byte[] OtherVideoBox(in List<H264NALU> nalus, in string key,in bool keyframe) | |||||
{ | { | ||||
byte[] buffer = FMp4ArrayPool.Rent(CacheSize); | byte[] buffer = FMp4ArrayPool.Rent(CacheSize); | ||||
FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); | FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); | ||||
@@ -450,32 +430,25 @@ namespace JT1078.FMp4 | |||||
//mdat | //mdat | ||||
var mediaDataBox = new MediaDataBox(); | var mediaDataBox = new MediaDataBox(); | ||||
mediaDataBox.Data=new List<byte[]>(); | |||||
string key = string.Empty; | |||||
bool keyFrame = nalus[0].NALUHeader.KeyFrame; | |||||
mediaDataBox.Data = new List<byte[]>(); | |||||
uint sampleSize = 0; | |||||
var truns = new List<TrackRunBox.TrackRunInfo>(); | var truns = new List<TrackRunBox.TrackRunInfo>(); | ||||
uint defaultSampleDuration = 0; | |||||
foreach (var n in nalus) | foreach (var n in nalus) | ||||
{ | { | ||||
if (key==string.Empty) | |||||
{ | |||||
key=n.GetKey(); | |||||
} | |||||
defaultSampleDuration+=DefaultSampleDuration; | |||||
truns.Add(new TrackRunBox.TrackRunInfo() | |||||
{ | |||||
SampleDuration = DefaultSampleDuration, | |||||
SampleSize = (uint)(n.RawData.Length+n.StartCodePrefix.Length), | |||||
}); | |||||
sampleSize += (uint)(n.RawData.Length + n.StartCodePrefix.Length); | |||||
mediaDataBox.Data.Add(n.RawData); | mediaDataBox.Data.Add(n.RawData); | ||||
} | } | ||||
truns.Add(new TrackRunBox.TrackRunInfo() | |||||
{ | |||||
SampleDuration = DefaultSampleDuration, | |||||
SampleSize = sampleSize, | |||||
}); | |||||
if (!TrackInfos.TryGetValue(key, out TrackInfo trackInfo)) | if (!TrackInfos.TryGetValue(key, out TrackInfo trackInfo)) | ||||
{ | { | ||||
trackInfo = new TrackInfo { SN = 1, SubsegmentDuration=0 }; | |||||
trackInfo = new TrackInfo { SN = 1, SubsegmentDuration = 0 }; | |||||
TrackInfos.Add(key, trackInfo); | TrackInfos.Add(key, trackInfo); | ||||
} | } | ||||
if (trackInfo.SN == uint.MaxValue) | |||||
if (trackInfo.SN == 0) | |||||
{ | { | ||||
trackInfo.SN = 1; | trackInfo.SN = 1; | ||||
} | } | ||||
@@ -487,7 +460,7 @@ namespace JT1078.FMp4 | |||||
{ | { | ||||
new SegmentIndexBox.SegmentIndex | new SegmentIndexBox.SegmentIndex | ||||
{ | { | ||||
SubsegmentDuration=defaultSampleDuration | |||||
SubsegmentDuration=DefaultSampleDuration | |||||
} | } | ||||
}; | }; | ||||
segmentIndexBox.ToBuffer(ref writer); | segmentIndexBox.ToBuffer(ref writer); | ||||
@@ -501,31 +474,36 @@ namespace JT1078.FMp4 | |||||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox = new TrackFragmentHeaderBox(TfhdFlags); | movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox = new TrackFragmentHeaderBox(TfhdFlags); | ||||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.TrackID = VideoTrackID; | movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.TrackID = VideoTrackID; | ||||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.SampleDescriptionIndex = SampleDescriptionIndex; | movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.SampleDescriptionIndex = SampleDescriptionIndex; | ||||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleSize = truns[0].SampleSize; | |||||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleDuration = truns[0].SampleDuration; | |||||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleSize = sampleSize; | |||||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleDuration = DefaultSampleDuration; | |||||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleFlags = FMp4Constants.TFHD_FLAG_VIDEO_TPYE; | movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleFlags = FMp4Constants.TFHD_FLAG_VIDEO_TPYE; | ||||
movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox = new TrackFragmentBaseMediaDecodeTimeBox(); | movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox = new TrackFragmentBaseMediaDecodeTimeBox(); | ||||
movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox.BaseMediaDecodeTime = trackInfo.SubsegmentDuration; | movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox.BaseMediaDecodeTime = trackInfo.SubsegmentDuration; | ||||
//update trackInfo | //update trackInfo | ||||
trackInfo.SubsegmentDuration+=defaultSampleDuration; | |||||
trackInfo.SubsegmentDuration += DefaultSampleDuration; | |||||
trackInfo.SN++; | trackInfo.SN++; | ||||
TrackInfos[key] = trackInfo; | TrackInfos[key] = trackInfo; | ||||
//trun | //trun | ||||
movieFragmentBox.TrackFragmentBox.TrackRunBox = new TrackRunBox(1, flags: TrunFlags); | |||||
if (keyframe) | |||||
{ | |||||
movieFragmentBox.TrackFragmentBox.TrackRunBox = new TrackRunBox(1, flags: | |||||
FMp4Constants.TRUN_FLAG_DATA_OFFSET_PRESENT | | |||||
FMp4Constants.TRUN_FLAG_FIRST_SAMPLE_FLAGS_PRESENT); | |||||
} | |||||
else | |||||
{ | |||||
movieFragmentBox.TrackFragmentBox.TrackRunBox = new TrackRunBox(1, FMp4Constants.TRUN_FLAG_DATA_OFFSET_PRESENT); | |||||
} | |||||
movieFragmentBox.TrackFragmentBox.TrackRunBox.FirstSampleFlags = FMp4Constants.TREX_FLAG_SAMPLE_DEPENDS_ON_I_PICTURE; | movieFragmentBox.TrackFragmentBox.TrackRunBox.FirstSampleFlags = FMp4Constants.TREX_FLAG_SAMPLE_DEPENDS_ON_I_PICTURE; | ||||
movieFragmentBox.TrackFragmentBox.TrackRunBox.TrackRunInfos = truns; | movieFragmentBox.TrackFragmentBox.TrackRunBox.TrackRunInfos = truns; | ||||
movieFragmentBox.ToBuffer(ref writer); | movieFragmentBox.ToBuffer(ref writer); | ||||
//mdat | //mdat | ||||
mediaDataBox.ToBuffer(ref writer); | mediaDataBox.ToBuffer(ref writer); | ||||
var current2 = writer.GetCurrentPosition(); | var current2 = writer.GetCurrentPosition(); | ||||
foreach (var postion in segmentIndexBox.ReferencedSizePositions) | foreach (var postion in segmentIndexBox.ReferencedSizePositions) | ||||
{ | { | ||||
writer.WriteUInt32Return((uint)(current2 - current1), postion); | writer.WriteUInt32Return((uint)(current2 - current1), postion); | ||||
} | } | ||||
var data = writer.FlushAndGetArray(); | var data = writer.FlushAndGetArray(); | ||||
return data; | return data; | ||||
} | } | ||||
@@ -1348,26 +1348,6 @@ | |||||
FMp4编码 | FMp4编码 | ||||
fmp4 | fmp4 | ||||
stream data | stream data | ||||
ftyp | |||||
moov | |||||
mvex | |||||
trex Video | |||||
trex Audio | |||||
styp 1 | |||||
sidx 1 Video | |||||
sidx 1 Audio | |||||
moof 1 | |||||
traf 1 Video | |||||
traf 1 Audio | |||||
mdat 1 | |||||
... | |||||
styp n | |||||
sidx 1 Video | |||||
sidx n Audio | |||||
moof n | |||||
traf n Video | |||||
traf n Audio | |||||
mdat n | |||||
ref: https://www.w3.org/TR/mse-byte-stream-format-isobmff/#movie-fragment-relative-addressing | ref: https://www.w3.org/TR/mse-byte-stream-format-isobmff/#movie-fragment-relative-addressing | ||||
</summary> | </summary> | ||||
</member> | </member> | ||||
@@ -1404,16 +1384,15 @@ | |||||
</summary> | </summary> | ||||
<returns></returns> | <returns></returns> | ||||
</member> | </member> | ||||
<member name="M:JT1078.FMp4.FMp4Encoder.FirstVideoBox(JT1078.Protocol.H264.H264NALU@,JT1078.Protocol.H264.H264NALU@)"> | |||||
<member name="M:JT1078.FMp4.FMp4Encoder.FirstVideoBox(JT1078.Protocol.JT1078AVFrame@)"> | |||||
<summary> | <summary> | ||||
编码首帧 | 编码首帧 | ||||
ftyp moov | ftyp moov | ||||
</summary> | </summary> | ||||
<param name="sps"></param> | |||||
<param name="pps"></param> | |||||
<param name="avframe"></param> | |||||
<returns></returns> | <returns></returns> | ||||
</member> | </member> | ||||
<member name="M:JT1078.FMp4.FMp4Encoder.OtherVideoBox(System.Collections.Generic.List{JT1078.Protocol.H264.H264NALU}@)"> | |||||
<member name="M:JT1078.FMp4.FMp4Encoder.OtherVideoBox(System.Collections.Generic.List{JT1078.Protocol.H264.H264NALU}@,System.String@,System.Boolean@)"> | |||||
<summary> | <summary> | ||||
编码其他视频数据盒子 | 编码其他视频数据盒子 | ||||
styp sidx moof mdat | styp sidx moof mdat | ||||
@@ -5,13 +5,13 @@ | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> | |||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" /> | |||||
<PackageReference Include="xunit" Version="2.4.1" /> | <PackageReference Include="xunit" Version="2.4.1" /> | ||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> | |||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> | |||||
<PrivateAssets>all</PrivateAssets> | <PrivateAssets>all</PrivateAssets> | ||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
</PackageReference> | </PackageReference> | ||||
<PackageReference Include="coverlet.collector" Version="3.1.0"> | |||||
<PackageReference Include="coverlet.collector" Version="3.1.2"> | |||||
<PrivateAssets>all</PrivateAssets> | <PrivateAssets>all</PrivateAssets> | ||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
</PackageReference> | </PackageReference> | ||||
@@ -68,6 +68,7 @@ namespace JT1078.Flv | |||||
/// <param name="hasAudio">是否有音频</param> | /// <param name="hasAudio">是否有音频</param> | ||||
/// <param name="frameRate">帧率 默认25d 即每秒25帧</param> | /// <param name="frameRate">帧率 默认25d 即每秒25帧</param> | ||||
/// <returns></returns> | /// <returns></returns> | ||||
[Obsolete("use EncoderScriptTag(JT1078AVFrame avframe, bool hasAudio = false, double frameRate = 25d)")] | |||||
public byte[] EncoderScriptTag(SPSInfo spsInfo, bool hasAudio = false, double frameRate = 25d) | public byte[] EncoderScriptTag(SPSInfo spsInfo, bool hasAudio = false, double frameRate = 25d) | ||||
{ | { | ||||
byte[] buffer = FlvArrayPool.Rent(1024); | byte[] buffer = FlvArrayPool.Rent(1024); | ||||
@@ -114,6 +115,60 @@ namespace JT1078.Flv | |||||
} | } | ||||
} | } | ||||
/// <summary> | |||||
/// 编码脚本Tag | |||||
/// </summary> | |||||
/// <param name="avframe">解析后的av信息</param> | |||||
/// <param name="hasAudio">是否有音频</param> | |||||
/// <param name="frameRate">帧率 默认25d 即每秒25帧</param> | |||||
/// <returns></returns> | |||||
public byte[] EncoderScriptTag(JT1078AVFrame avframe, bool hasAudio = false, double frameRate = 25d) | |||||
{ | |||||
byte[] buffer = FlvArrayPool.Rent(1024); | |||||
try | |||||
{ | |||||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | |||||
//flv body script tag | |||||
//flv body tag header | |||||
FlvTags flvTags = new FlvTags | |||||
{ | |||||
Type = TagType.ScriptData, | |||||
//flv body tag body | |||||
DataTagsData = new Amf3 | |||||
{ | |||||
Amf3Metadatas = new List<IAmf3Metadata> | |||||
{ | |||||
new Amf3Metadata_Duration{Value = 0d}, | |||||
new Amf3Metadata_VideoDataRate{Value = 0d}, | |||||
new Amf3Metadata_VideoCodecId{Value = 7d}, | |||||
new Amf3Metadata_FrameRate{Value = frameRate}, | |||||
new Amf3Metadata_Width(){ | |||||
Value=avframe.Width | |||||
}, | |||||
new Amf3Metadata_Height(){ | |||||
Value=avframe.Height | |||||
}, | |||||
} | |||||
} | |||||
}; | |||||
if (hasAudio) | |||||
{ | |||||
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_AudioCodecId()); | |||||
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_AudioSampleRate()); | |||||
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_AudioSampleSize()); | |||||
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_AudioStereo()); | |||||
} | |||||
flvMessagePackWriter.WriteFlvTag(flvTags); | |||||
flvMessagePackWriter.WriteUInt32((uint)(flvTags.DataSize + 11)); | |||||
return flvMessagePackWriter.FlushAndGetArray(); | |||||
} | |||||
finally | |||||
{ | |||||
FlvArrayPool.Return(buffer); | |||||
} | |||||
} | |||||
/// <summary> | /// <summary> | ||||
/// 编码首帧视频,即videoTag[0] | /// 编码首帧视频,即videoTag[0] | ||||
/// </summary> | /// </summary> | ||||
@@ -122,6 +177,7 @@ namespace JT1078.Flv | |||||
/// <param name="pps"></param> | /// <param name="pps"></param> | ||||
/// <param name="sei"></param> | /// <param name="sei"></param> | ||||
/// <returns></returns> | /// <returns></returns> | ||||
[Obsolete("use EncoderFirstVideoTag(JT1078AVFrame avframe)")] | |||||
public byte[] EncoderFirstVideoTag(SPSInfo spsInfo, H264NALU sps, H264NALU pps, H264NALU sei) | public byte[] EncoderFirstVideoTag(SPSInfo spsInfo, H264NALU sps, H264NALU pps, H264NALU sei) | ||||
{ | { | ||||
byte[] buffer = FlvArrayPool.Rent(4096); | byte[] buffer = FlvArrayPool.Rent(4096); | ||||
@@ -165,6 +221,54 @@ namespace JT1078.Flv | |||||
} | } | ||||
} | } | ||||
/// <summary> | |||||
/// 编码首帧视频,即videoTag[0] | |||||
/// </summary> | |||||
/// <param name="avframe"></param> | |||||
/// <returns></returns> | |||||
public byte[] EncoderFirstVideoTag(JT1078AVFrame avframe) | |||||
{ | |||||
byte[] buffer = FlvArrayPool.Rent(4096); | |||||
try | |||||
{ | |||||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | |||||
//flv body video tag | |||||
//flv body tag header | |||||
FlvTags flvTags = new FlvTags | |||||
{ | |||||
Type = TagType.Video, | |||||
Timestamp = (uint)avframe.SPS.Timestamp, | |||||
TimestampExt = 0, | |||||
StreamId = 0, | |||||
//flv body tag body | |||||
VideoTagsData = new VideoTags() | |||||
}; | |||||
flvTags.VideoTagsData.FrameType = FrameType.KeyFrame; | |||||
flvTags.VideoTagsData.VideoData = new AvcVideoPacke | |||||
{ | |||||
AvcPacketType = AvcPacketType.SequenceHeader, | |||||
CompositionTime = 0 | |||||
}; | |||||
AVCDecoderConfigurationRecord aVCDecoderConfigurationRecord = new AVCDecoderConfigurationRecord | |||||
{ | |||||
AVCProfileIndication = avframe.ProfileIdc, | |||||
ProfileCompatibility = avframe.ProfileCompat, | |||||
AVCLevelIndication = avframe.LevelIdc, | |||||
NumOfPictureParameterSets = 1, | |||||
PPSBuffer = avframe.PPS.RawData, | |||||
SPSBuffer = avframe.SPS.RawData | |||||
}; | |||||
flvTags.VideoTagsData.VideoData.AVCDecoderConfiguration = aVCDecoderConfigurationRecord; | |||||
flvMessagePackWriter.WriteFlvTag(flvTags); | |||||
flvMessagePackWriter.WriteUInt32((uint)(flvTags.DataSize + 11)); | |||||
return flvMessagePackWriter.FlushAndGetArray(); | |||||
} | |||||
finally | |||||
{ | |||||
FlvArrayPool.Return(buffer); | |||||
} | |||||
} | |||||
/// <summary> | /// <summary> | ||||
/// 编码首帧音频,即audioTag[0] | /// 编码首帧音频,即audioTag[0] | ||||
/// </summary> | /// </summary> | ||||
@@ -203,6 +203,15 @@ | |||||
<param name="frameRate">帧率 默认25d 即每秒25帧</param> | <param name="frameRate">帧率 默认25d 即每秒25帧</param> | ||||
<returns></returns> | <returns></returns> | ||||
</member> | </member> | ||||
<member name="M:JT1078.Flv.FlvEncoder.EncoderScriptTag(JT1078.Protocol.JT1078AVFrame,System.Boolean,System.Double)"> | |||||
<summary> | |||||
编码脚本Tag | |||||
</summary> | |||||
<param name="avframe">解析后的av信息</param> | |||||
<param name="hasAudio">是否有音频</param> | |||||
<param name="frameRate">帧率 默认25d 即每秒25帧</param> | |||||
<returns></returns> | |||||
</member> | |||||
<member name="M:JT1078.Flv.FlvEncoder.EncoderFirstVideoTag(JT1078.Protocol.MessagePack.SPSInfo,JT1078.Protocol.H264.H264NALU,JT1078.Protocol.H264.H264NALU,JT1078.Protocol.H264.H264NALU)"> | <member name="M:JT1078.Flv.FlvEncoder.EncoderFirstVideoTag(JT1078.Protocol.MessagePack.SPSInfo,JT1078.Protocol.H264.H264NALU,JT1078.Protocol.H264.H264NALU,JT1078.Protocol.H264.H264NALU)"> | ||||
<summary> | <summary> | ||||
编码首帧视频,即videoTag[0] | 编码首帧视频,即videoTag[0] | ||||
@@ -213,6 +222,13 @@ | |||||
<param name="sei"></param> | <param name="sei"></param> | ||||
<returns></returns> | <returns></returns> | ||||
</member> | </member> | ||||
<member name="M:JT1078.Flv.FlvEncoder.EncoderFirstVideoTag(JT1078.Protocol.JT1078AVFrame)"> | |||||
<summary> | |||||
编码首帧视频,即videoTag[0] | |||||
</summary> | |||||
<param name="avframe"></param> | |||||
<returns></returns> | |||||
</member> | |||||
<member name="M:JT1078.Flv.FlvEncoder.EncoderFirstAudioTag(System.UInt64)"> | <member name="M:JT1078.Flv.FlvEncoder.EncoderFirstAudioTag(System.UInt64)"> | ||||
<summary> | <summary> | ||||
编码首帧音频,即audioTag[0] | 编码首帧音频,即audioTag[0] | ||||
@@ -15,14 +15,14 @@ | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.0" /> | |||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> | |||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.5" /> | |||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" /> | |||||
<PackageReference Include="xunit" Version="2.4.1" /> | <PackageReference Include="xunit" Version="2.4.1" /> | ||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> | |||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> | |||||
<PrivateAssets>all</PrivateAssets> | <PrivateAssets>all</PrivateAssets> | ||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
</PackageReference> | </PackageReference> | ||||
<PackageReference Include="coverlet.collector" Version="3.1.0"> | |||||
<PackageReference Include="coverlet.collector" Version="3.1.2"> | |||||
<PrivateAssets>all</PrivateAssets> | <PrivateAssets>all</PrivateAssets> | ||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
</PackageReference> | </PackageReference> | ||||
@@ -12,7 +12,7 @@ | |||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" /> | <PackageReference Include="BenchmarkDotNet" Version="0.13.1" /> | ||||
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.1" /> | <PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.1" /> | ||||
<PackageReference Include="System.Buffers" Version="4.5.1" /> | <PackageReference Include="System.Buffers" Version="4.5.1" /> | ||||
<PackageReference Include="System.Memory" Version="4.5.4" /> | |||||
<PackageReference Include="System.Memory" Version="4.5.5" /> | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<ProjectReference Include="..\JT1078.Flv\JT1078.Flv.csproj" /> | <ProjectReference Include="..\JT1078.Flv\JT1078.Flv.csproj" /> | ||||
@@ -7,10 +7,10 @@ | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="JT808" Version="2.4.5" /> | |||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> | |||||
<PackageReference Include="JT808" Version="2.5.0-preview1" /> | |||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" /> | |||||
<PackageReference Include="xunit" Version="2.4.1" /> | <PackageReference Include="xunit" Version="2.4.1" /> | ||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> | |||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> | |||||
<PrivateAssets>all</PrivateAssets> | <PrivateAssets>all</PrivateAssets> | ||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
</PackageReference> | </PackageReference> | ||||
@@ -1,4 +1,5 @@ | |||||
using System; | |||||
using JT1078.Protocol.MessagePack; | |||||
using System; | |||||
using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Linq; | using System.Linq; | ||||
@@ -67,6 +68,99 @@ namespace JT1078.Protocol.H264 | |||||
return h264NALUs; | return h264NALUs; | ||||
} | } | ||||
/// <summary> | |||||
/// | |||||
/// </summary> | |||||
/// <param name="package"></param> | |||||
/// <param name="key"></param> | |||||
/// <returns></returns> | |||||
public JT1078AVFrame ParseAVFrame(JT1078Package package, string key = null) | |||||
{ | |||||
JT1078AVFrame jT1078AVFrame = new JT1078AVFrame(); | |||||
jT1078AVFrame.LogicChannelNumber = package.LogicChannelNumber; | |||||
jT1078AVFrame.SIM = package.SIM; | |||||
jT1078AVFrame.Nalus = new List<H264NALU>(); | |||||
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); | |||||
H264NALU nalu = Create(package, tmp, state + 1); | |||||
if (nalu.NALUHeader.NalUnitType == NalUnitType.PPS) | |||||
{ | |||||
jT1078AVFrame.PPS = nalu; | |||||
} | |||||
else if (nalu.NALUHeader.NalUnitType == NalUnitType.SPS) | |||||
{ | |||||
jT1078AVFrame.SPS = nalu; | |||||
ExpGolombReader h264GolombReader = new ExpGolombReader(jT1078AVFrame.SPS.RawData); | |||||
var spsInfo = h264GolombReader.ReadSPS(); | |||||
jT1078AVFrame.Width = spsInfo.width; | |||||
jT1078AVFrame.Height = spsInfo.height; | |||||
jT1078AVFrame.LevelIdc= spsInfo.levelIdc; | |||||
jT1078AVFrame.ProfileIdc=spsInfo.profileIdc; | |||||
jT1078AVFrame.ProfileCompat=(byte)spsInfo.profileCompat; | |||||
} | |||||
jT1078AVFrame.Nalus.Add(nalu); | |||||
} | |||||
lastIndex = i; | |||||
laststate = state + 1; | |||||
state = 0; | |||||
} | |||||
else | |||||
{ | |||||
state = 0; | |||||
} | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
if (lastIndex.HasValue) | |||||
{ | |||||
H264NALU nalu = Create(package, buffer.Slice(lastIndex.Value), laststate); | |||||
if(nalu.NALUHeader.NalUnitType== NalUnitType.PPS) | |||||
{ | |||||
jT1078AVFrame.PPS = nalu; | |||||
} | |||||
else if(nalu.NALUHeader.NalUnitType == NalUnitType.SPS) | |||||
{ | |||||
jT1078AVFrame.SPS = nalu; | |||||
ExpGolombReader h264GolombReader = new ExpGolombReader(jT1078AVFrame.SPS.RawData); | |||||
var spsInfo = h264GolombReader.ReadSPS(); | |||||
jT1078AVFrame.Width = spsInfo.width; | |||||
jT1078AVFrame.Height = spsInfo.height; | |||||
jT1078AVFrame.LevelIdc = spsInfo.levelIdc; | |||||
jT1078AVFrame.ProfileIdc = spsInfo.profileIdc; | |||||
jT1078AVFrame.ProfileCompat = (byte)spsInfo.profileCompat; | |||||
} | |||||
jT1078AVFrame.Nalus.Add(nalu); | |||||
} | |||||
return jT1078AVFrame; | |||||
} | |||||
/// <summary> | /// <summary> | ||||
/// | /// | ||||
/// <see cref="https://github.com/samirkumardas/jmuxer/blob/master/src/parsers/h264.js"/> | /// <see cref="https://github.com/samirkumardas/jmuxer/blob/master/src/parsers/h264.js"/> | ||||
@@ -5,7 +5,7 @@ using System.Text; | |||||
namespace JT1078.Protocol.H264 | namespace JT1078.Protocol.H264 | ||||
{ | { | ||||
public class H264NALU | |||||
public class H264NALU: IJT1078AVKey | |||||
{ | { | ||||
public readonly static byte[] Start1 = new byte[3] { 0, 0, 1 }; | public readonly static byte[] Start1 = new byte[3] { 0, 0, 1 }; | ||||
public readonly static byte[] Start2 = new byte[4] { 0, 0, 0, 1 }; | public readonly static byte[] Start2 = new byte[4] { 0, 0, 0, 1 }; | ||||
@@ -44,6 +44,13 @@ namespace JT1078.Protocol.H264 | |||||
/// 数据体 | /// 数据体 | ||||
/// </summary> | /// </summary> | ||||
public byte[] RawData { get; set; } | public byte[] RawData { get; set; } | ||||
public string GetAVKey() | |||||
{ | |||||
return $"{SIM}_{LogicChannelNumber.ToString()}"; | |||||
} | |||||
[Obsolete("use GetAVKey")] | |||||
public string GetKey() | public string GetKey() | ||||
{ | { | ||||
return $"{SIM}_{LogicChannelNumber.ToString()}"; | return $"{SIM}_{LogicChannelNumber.ToString()}"; | ||||
@@ -0,0 +1,13 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace JT1078.Protocol | |||||
{ | |||||
public interface IJT1078AVKey | |||||
{ | |||||
string GetAVKey(); | |||||
} | |||||
} |
@@ -122,6 +122,14 @@ | |||||
<param name="key"></param> | <param name="key"></param> | ||||
<returns></returns> | <returns></returns> | ||||
</member> | </member> | ||||
<member name="M:JT1078.Protocol.H264.H264Decoder.ParseAVFrame(JT1078.Protocol.JT1078Package,System.String)"> | |||||
<summary> | |||||
</summary> | |||||
<param name="package"></param> | |||||
<param name="key"></param> | |||||
<returns></returns> | |||||
</member> | |||||
<member name="M:JT1078.Protocol.H264.H264Decoder.ParseNALU(JT1078.Protocol.JT1078Package,System.Collections.Generic.List{JT1078.Protocol.H264.H264NALU},System.String)"> | <member name="M:JT1078.Protocol.H264.H264Decoder.ParseNALU(JT1078.Protocol.JT1078Package,System.Collections.Generic.List{JT1078.Protocol.H264.H264NALU},System.String)"> | ||||
<summary> | <summary> | ||||
@@ -181,6 +189,17 @@ | |||||
数据体 | 数据体 | ||||
</summary> | </summary> | ||||
</member> | </member> | ||||
<member name="P:JT1078.Protocol.JT1078AVFrame.SIM"> | |||||
<summary> | |||||
终端设备SIM卡号 | |||||
BCD[6] | |||||
</summary> | |||||
</member> | |||||
<member name="P:JT1078.Protocol.JT1078AVFrame.LogicChannelNumber"> | |||||
<summary> | |||||
逻辑通道号 | |||||
</summary> | |||||
</member> | |||||
<member name="T:JT1078.Protocol.JT1078Label1"> | <member name="T:JT1078.Protocol.JT1078Label1"> | ||||
<summary> | <summary> | ||||
V - 2 - 固定为2 | V - 2 - 固定为2 | ||||
@@ -322,7 +341,7 @@ | |||||
</member> | </member> | ||||
<member name="P:JT1078.Protocol.JT1078Package.LastFrameInterval"> | <member name="P:JT1078.Protocol.JT1078Package.LastFrameInterval"> | ||||
<summary> | <summary> | ||||
该帧与上一个关键帧之间的时间间隔,单位毫秒(ms), | |||||
该帧与上一帧之间的时间间隔,单位毫秒(ms), | |||||
当数据类型为非视频帧时,则没有该字段 | 当数据类型为非视频帧时,则没有该字段 | ||||
</summary> | </summary> | ||||
</member> | </member> | ||||
@@ -0,0 +1,39 @@ | |||||
using JT1078.Protocol.H264; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace JT1078.Protocol | |||||
{ | |||||
public class JT1078AVFrame : IJT1078AVKey | |||||
{ | |||||
/// <summary> | |||||
/// 终端设备SIM卡号 | |||||
/// BCD[6] | |||||
/// </summary> | |||||
public string SIM { get; set; } | |||||
/// <summary> | |||||
/// 逻辑通道号 | |||||
/// </summary> | |||||
public byte LogicChannelNumber { get; set; } | |||||
public List<H264NALU> Nalus { get; set; } | |||||
public H264NALU SPS { get; set; } | |||||
public H264NALU PPS { get; set; } | |||||
public int Width { get; set; } | |||||
public int Height { get; set; } | |||||
string VideoType { get; set; } = "avc1"; | |||||
public byte ProfileIdc { get; set; } | |||||
public byte ProfileCompat { get; set; } | |||||
public byte LevelIdc { get; set; } | |||||
public string ToCodecs() | |||||
{ | |||||
return $"{VideoType}.{ProfileIdc:x2}{ProfileCompat:x2}{LevelIdc:x2}"; | |||||
} | |||||
public string GetAVKey() | |||||
{ | |||||
return $"{SIM}_{LogicChannelNumber.ToString()}"; | |||||
} | |||||
} | |||||
} |
@@ -4,7 +4,7 @@ using System.Text; | |||||
namespace JT1078.Protocol | namespace JT1078.Protocol | ||||
{ | { | ||||
public class JT1078Package | |||||
public class JT1078Package: IJT1078AVKey | |||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// 帧头标识 | /// 帧头标识 | ||||
@@ -74,7 +74,7 @@ namespace JT1078.Protocol | |||||
/// </summary> | /// </summary> | ||||
public ushort LastIFrameInterval { get; set; } | public ushort LastIFrameInterval { get; set; } | ||||
/// <summary> | /// <summary> | ||||
/// 该帧与上一个关键帧之间的时间间隔,单位毫秒(ms), | |||||
/// 该帧与上一帧之间的时间间隔,单位毫秒(ms), | |||||
/// 当数据类型为非视频帧时,则没有该字段 | /// 当数据类型为非视频帧时,则没有该字段 | ||||
/// </summary> | /// </summary> | ||||
public ushort LastFrameInterval { get; set; } | public ushort LastFrameInterval { get; set; } | ||||
@@ -87,9 +87,15 @@ namespace JT1078.Protocol | |||||
/// </summary> | /// </summary> | ||||
public byte[] Bodies{ get; set; } | public byte[] Bodies{ get; set; } | ||||
[Obsolete("use GetAVKey()")] | |||||
public string GetKey() | public string GetKey() | ||||
{ | { | ||||
return $"{SIM}_{LogicChannelNumber.ToString()}"; | return $"{SIM}_{LogicChannelNumber.ToString()}"; | ||||
} | } | ||||
public string GetAVKey() | |||||
{ | |||||
return $"{SIM}_{LogicChannelNumber.ToString()}"; | |||||
} | |||||
} | } | ||||
} | } |
@@ -18,6 +18,7 @@ using System.IO; | |||||
using JT1078.Protocol.Extensions; | using JT1078.Protocol.Extensions; | ||||
using JT1078.Protocol.H264; | using JT1078.Protocol.H264; | ||||
using System.Net.WebSockets; | using System.Net.WebSockets; | ||||
using JT1078.Protocol.Enums; | |||||
namespace JT1078.SignalR.Test.Services | namespace JT1078.SignalR.Test.Services | ||||
{ | { | ||||
@@ -49,7 +50,7 @@ namespace JT1078.SignalR.Test.Services | |||||
public List<byte[]> q = new List<byte[]>(); | public List<byte[]> q = new List<byte[]>(); | ||||
public void a() | |||||
void Init() | |||||
{ | { | ||||
List<JT1078Package> packages = new List<JT1078Package>(); | List<JT1078Package> packages = new List<JT1078Package>(); | ||||
var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "jt1078_6.txt")); | var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "jt1078_6.txt")); | ||||
@@ -66,12 +67,10 @@ namespace JT1078.SignalR.Test.Services | |||||
} | } | ||||
} | } | ||||
var nalus1 = h264Decoder.ParseNALU(packages[0]); | |||||
q.Add(fMp4Encoder.FirstVideoBox( | |||||
nalus1.FirstOrDefault(f => f.NALUHeader.NalUnitType == NalUnitType.SPS), | |||||
nalus1.FirstOrDefault(f => f.NALUHeader.NalUnitType == NalUnitType.PPS))); | |||||
var avframe = h264Decoder.ParseAVFrame(packages[0]); | |||||
q.Add(fMp4Encoder.FirstVideoBox(avframe)); | |||||
List<H264NALU> stream = new List<H264NALU>(); | |||||
Queue<Mp4Frame> mp4Frames = new Queue<Mp4Frame>(); | |||||
List<NalUnitType> filter = new List<NalUnitType>(); | List<NalUnitType> filter = new List<NalUnitType>(); | ||||
filter.Add(NalUnitType.SEI); | filter.Add(NalUnitType.SEI); | ||||
filter.Add(NalUnitType.PPS); | filter.Add(NalUnitType.PPS); | ||||
@@ -80,47 +79,35 @@ namespace JT1078.SignalR.Test.Services | |||||
foreach (var package in packages) | foreach (var package in packages) | ||||
{ | { | ||||
List<H264NALU> h264NALUs = h264Decoder.ParseNALU(package); | List<H264NALU> h264NALUs = h264Decoder.ParseNALU(package); | ||||
if (h264NALUs!=null && h264NALUs.Count>0) | |||||
if (h264NALUs != null && h264NALUs.Count > 0) | |||||
{ | { | ||||
stream.AddRange(h264NALUs.Where(w => !filter.Contains(w.NALUHeader.NalUnitType))); | |||||
Mp4Frame mp4Frame = new Mp4Frame | |||||
{ | |||||
Key = package.GetKey(), | |||||
KeyFrame = package.Label3.DataType == JT1078DataType.视频I帧 | |||||
}; | |||||
mp4Frame.NALUs = h264NALUs; | |||||
mp4Frames.Enqueue(mp4Frame); | |||||
} | } | ||||
} | } | ||||
List<H264NALU> tmp = new List<H264NALU>(); | |||||
H264NALU prevNalu = null; | |||||
foreach (var item in stream) | |||||
while (mp4Frames.TryDequeue(out Mp4Frame frame)) | |||||
{ | { | ||||
if (item.NALUHeader.KeyFrame) | |||||
{ | |||||
if (tmp.Count>0) | |||||
{ | |||||
q.Add(fMp4Encoder.OtherVideoBox(tmp)); | |||||
tmp.Clear(); | |||||
} | |||||
tmp.Add(item); | |||||
q.Add(fMp4Encoder.OtherVideoBox(tmp)); | |||||
tmp.Clear(); | |||||
prevNalu=item; | |||||
continue; | |||||
} | |||||
if (prevNalu!=null) //第一帧I帧 | |||||
{ | |||||
if (tmp.Count>1) | |||||
{ | |||||
q.Add(fMp4Encoder.OtherVideoBox(tmp)); | |||||
tmp.Clear(); | |||||
} | |||||
tmp.Add(item); | |||||
} | |||||
q.Add(fMp4Encoder.OtherVideoBox(frame.NALUs, frame.Key, frame.KeyFrame)); | |||||
} | } | ||||
} | } | ||||
class Mp4Frame | |||||
{ | |||||
public string Key { get; set; } | |||||
public bool KeyFrame { get; set; } | |||||
public List<H264NALU> NALUs { get; set; } | |||||
} | |||||
public Dictionary<string,int> flag = new Dictionary<string, int>(); | public Dictionary<string,int> flag = new Dictionary<string, int>(); | ||||
protected async override Task ExecuteAsync(CancellationToken stoppingToken) | protected async override Task ExecuteAsync(CancellationToken stoppingToken) | ||||
{ | { | ||||
a(); | |||||
Init(); | |||||
while (!stoppingToken.IsCancellationRequested) | while (!stoppingToken.IsCancellationRequested) | ||||
{ | { | ||||
try | try | ||||
@@ -149,7 +136,7 @@ namespace JT1078.SignalR.Test.Services | |||||
{ | { | ||||
logger.LogError(ex,""); | logger.LogError(ex,""); | ||||
} | } | ||||
await Task.Delay(80); | |||||
await Task.Delay(60); | |||||
} | } | ||||
} | } | ||||
} | } | ||||