@@ -89,8 +89,8 @@ var hex = JT1078Serializer.Serialize(jT1078Package).ToHexString(); | |||
2.拆解: | |||
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 包序号 | |||
01 12 34 56 78 10 --SIM卡号 | |||
01 --逻辑通道号 | |||
@@ -8,7 +8,7 @@ | |||
<PackageProjectUrl>https://github.com/SmallChi/JT1078</PackageProjectUrl> | |||
<licenseUrl>https://github.com/SmallChi/JT1078/blob/master/LICENSE</licenseUrl> | |||
<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> | |||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> | |||
<AnalysisLevel>latest</AnalysisLevel> | |||
@@ -8,7 +8,7 @@ | |||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" /> | |||
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.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> | |||
<ProjectReference Include="..\JT1078.Flv\JT1078.Flv.csproj" /> | |||
@@ -7,13 +7,13 @@ | |||
</PropertyGroup> | |||
<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.runner.visualstudio" Version="2.4.3"> | |||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> | |||
<PrivateAssets>all</PrivateAssets> | |||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||
</PackageReference> | |||
<PackageReference Include="coverlet.collector" Version="3.1.0"> | |||
<PackageReference Include="coverlet.collector" Version="3.1.2"> | |||
<PrivateAssets>all</PrivateAssets> | |||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||
</PackageReference> | |||
@@ -12,6 +12,7 @@ using System.Buffers.Binary; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Net.Sockets; | |||
using System.Text; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
@@ -498,12 +499,15 @@ namespace JT1078.FMp4.Test | |||
} | |||
[Fact] | |||
public void Test4_3() | |||
public void Test4_4() | |||
{ | |||
uint a = uint.MaxValue; | |||
var b = a + 1; | |||
FMp4Encoder fMp4Encoder = new FMp4Encoder(); | |||
H264Decoder h264Decoder = new H264Decoder(); | |||
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)) | |||
{ | |||
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.PPS)); | |||
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>(); | |||
filter.Add(NalUnitType.SEI); | |||
filter.Add(NalUnitType.PPS); | |||
@@ -531,53 +534,42 @@ namespace JT1078.FMp4.Test | |||
foreach (var package in packages) | |||
{ | |||
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(); | |||
} | |||
} | |||
class Mp4Frame | |||
{ | |||
public string Key { get; set; } | |||
public bool KeyFrame { get; set; } | |||
public List<H264NALU> NALUs { get; set; } | |||
} | |||
[Fact] | |||
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)) | |||
{ | |||
File.Delete(filepath); | |||
} | |||
using var fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | |||
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() => | |||
{ | |||
while (true) | |||
@@ -16,26 +16,6 @@ namespace JT1078.FMp4 | |||
/// FMp4编码 | |||
/// fmp4 | |||
/// 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 | |||
/// </summary> | |||
public class FMp4Encoder | |||
@@ -49,7 +29,7 @@ namespace JT1078.FMp4 | |||
FMp4Constants.TFHD_FLAG_DEFAULT_FLAGS; | |||
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; | |||
const uint SampleDescriptionIndex = 1; | |||
@@ -325,10 +305,9 @@ namespace JT1078.FMp4 | |||
/// 编码首帧 | |||
/// ftyp moov | |||
/// </summary> | |||
/// <param name="sps"></param> | |||
/// <param name="pps"></param> | |||
/// <param name="avframe"></param> | |||
/// <returns></returns> | |||
public byte[] FirstVideoBox(in H264NALU sps, in H264NALU pps) | |||
public byte[] FirstVideoBox(in JT1078AVFrame avframe) | |||
{ | |||
byte[] buffer = FMp4ArrayPool.Rent(CacheSize); | |||
FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); | |||
@@ -348,8 +327,6 @@ namespace JT1078.FMp4 | |||
fileTypeBox.CompatibleBrands.Add("iso6"); | |||
fileTypeBox.ToBuffer(ref writer); | |||
ExpGolombReader h264GolombReader = new ExpGolombReader(sps.RawData); | |||
var spsInfo = h264GolombReader.ReadSPS(); | |||
//moov | |||
MovieBox movieBox = new MovieBox(); | |||
movieBox.MovieHeaderBox = new MovieHeaderBox(0, 2); | |||
@@ -366,8 +343,8 @@ namespace JT1078.FMp4 | |||
movieBox.TrackBox.TrackHeaderBox.TrackID = VideoTrackID; | |||
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.TrackHeaderBox.Width = (uint)avframe.Width; | |||
movieBox.TrackBox.TrackHeaderBox.Height = (uint)avframe.Height; | |||
movieBox.TrackBox.MediaBox = new MediaBox(); | |||
movieBox.TrackBox.MediaBox.MediaHeaderBox = new MediaHeaderBox(); | |||
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.SampleDescriptionBox = new SampleDescriptionBox(); | |||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleDescriptionBox.SampleEntries = new List<SampleEntry>(); | |||
//h264 | |||
//h264 | |||
//profileIdc profileCompat levelIdc | |||
//0x64 0x00 0x1e | |||
//avc1.64001e | |||
AVC1SampleEntry avc1 = new AVC1SampleEntry(); | |||
avc1.AVCConfigurationBox = new AVCConfigurationBox(); | |||
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 }; | |||
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.TimeToSampleBox = new TimeToSampleBox(); | |||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SyncSampleBox = new SyncSampleBox(); | |||
@@ -430,7 +410,7 @@ namespace JT1078.FMp4 | |||
/// styp sidx moof mdat | |||
/// </summary> | |||
/// <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); | |||
FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); | |||
@@ -450,32 +430,25 @@ namespace JT1078.FMp4 | |||
//mdat | |||
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>(); | |||
uint defaultSampleDuration = 0; | |||
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); | |||
} | |||
truns.Add(new TrackRunBox.TrackRunInfo() | |||
{ | |||
SampleDuration = DefaultSampleDuration, | |||
SampleSize = sampleSize, | |||
}); | |||
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); | |||
} | |||
if (trackInfo.SN == uint.MaxValue) | |||
if (trackInfo.SN == 0) | |||
{ | |||
trackInfo.SN = 1; | |||
} | |||
@@ -487,7 +460,7 @@ namespace JT1078.FMp4 | |||
{ | |||
new SegmentIndexBox.SegmentIndex | |||
{ | |||
SubsegmentDuration=defaultSampleDuration | |||
SubsegmentDuration=DefaultSampleDuration | |||
} | |||
}; | |||
segmentIndexBox.ToBuffer(ref writer); | |||
@@ -501,31 +474,36 @@ namespace JT1078.FMp4 | |||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox = new TrackFragmentHeaderBox(TfhdFlags); | |||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.TrackID = VideoTrackID; | |||
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.TrackFragmentBaseMediaDecodeTimeBox = new TrackFragmentBaseMediaDecodeTimeBox(); | |||
movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox.BaseMediaDecodeTime = trackInfo.SubsegmentDuration; | |||
//update trackInfo | |||
trackInfo.SubsegmentDuration+=defaultSampleDuration; | |||
trackInfo.SubsegmentDuration += DefaultSampleDuration; | |||
trackInfo.SN++; | |||
TrackInfos[key] = trackInfo; | |||
//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.TrackRunInfos = truns; | |||
movieFragmentBox.ToBuffer(ref writer); | |||
//mdat | |||
mediaDataBox.ToBuffer(ref writer); | |||
var current2 = writer.GetCurrentPosition(); | |||
foreach (var postion in segmentIndexBox.ReferencedSizePositions) | |||
{ | |||
writer.WriteUInt32Return((uint)(current2 - current1), postion); | |||
} | |||
var data = writer.FlushAndGetArray(); | |||
return data; | |||
} | |||
@@ -1348,26 +1348,6 @@ | |||
FMp4编码 | |||
fmp4 | |||
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 | |||
</summary> | |||
</member> | |||
@@ -1404,16 +1384,15 @@ | |||
</summary> | |||
<returns></returns> | |||
</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> | |||
编码首帧 | |||
ftyp moov | |||
</summary> | |||
<param name="sps"></param> | |||
<param name="pps"></param> | |||
<param name="avframe"></param> | |||
<returns></returns> | |||
</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> | |||
编码其他视频数据盒子 | |||
styp sidx moof mdat | |||
@@ -5,13 +5,13 @@ | |||
</PropertyGroup> | |||
<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.runner.visualstudio" Version="2.4.3"> | |||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> | |||
<PrivateAssets>all</PrivateAssets> | |||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||
</PackageReference> | |||
<PackageReference Include="coverlet.collector" Version="3.1.0"> | |||
<PackageReference Include="coverlet.collector" Version="3.1.2"> | |||
<PrivateAssets>all</PrivateAssets> | |||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||
</PackageReference> | |||
@@ -68,6 +68,7 @@ namespace JT1078.Flv | |||
/// <param name="hasAudio">是否有音频</param> | |||
/// <param name="frameRate">帧率 默认25d 即每秒25帧</param> | |||
/// <returns></returns> | |||
[Obsolete("use EncoderScriptTag(JT1078AVFrame avframe, bool hasAudio = false, double frameRate = 25d)")] | |||
public byte[] EncoderScriptTag(SPSInfo spsInfo, bool hasAudio = false, double frameRate = 25d) | |||
{ | |||
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> | |||
/// 编码首帧视频,即videoTag[0] | |||
/// </summary> | |||
@@ -122,6 +177,7 @@ namespace JT1078.Flv | |||
/// <param name="pps"></param> | |||
/// <param name="sei"></param> | |||
/// <returns></returns> | |||
[Obsolete("use EncoderFirstVideoTag(JT1078AVFrame avframe)")] | |||
public byte[] EncoderFirstVideoTag(SPSInfo spsInfo, H264NALU sps, H264NALU pps, H264NALU sei) | |||
{ | |||
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> | |||
/// 编码首帧音频,即audioTag[0] | |||
/// </summary> | |||
@@ -203,6 +203,15 @@ | |||
<param name="frameRate">帧率 默认25d 即每秒25帧</param> | |||
<returns></returns> | |||
</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)"> | |||
<summary> | |||
编码首帧视频,即videoTag[0] | |||
@@ -213,6 +222,13 @@ | |||
<param name="sei"></param> | |||
<returns></returns> | |||
</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)"> | |||
<summary> | |||
编码首帧音频,即audioTag[0] | |||
@@ -15,14 +15,14 @@ | |||
</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.runner.visualstudio" Version="2.4.3"> | |||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> | |||
<PrivateAssets>all</PrivateAssets> | |||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||
</PackageReference> | |||
<PackageReference Include="coverlet.collector" Version="3.1.0"> | |||
<PackageReference Include="coverlet.collector" Version="3.1.2"> | |||
<PrivateAssets>all</PrivateAssets> | |||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||
</PackageReference> | |||
@@ -12,7 +12,7 @@ | |||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" /> | |||
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.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> | |||
<ProjectReference Include="..\JT1078.Flv\JT1078.Flv.csproj" /> | |||
@@ -7,10 +7,10 @@ | |||
</PropertyGroup> | |||
<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.runner.visualstudio" Version="2.4.3"> | |||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> | |||
<PrivateAssets>all</PrivateAssets> | |||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||
</PackageReference> | |||
@@ -1,4 +1,5 @@ | |||
using System; | |||
using JT1078.Protocol.MessagePack; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
@@ -67,6 +68,99 @@ namespace JT1078.Protocol.H264 | |||
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> | |||
/// | |||
/// <see cref="https://github.com/samirkumardas/jmuxer/blob/master/src/parsers/h264.js"/> | |||
@@ -5,7 +5,7 @@ using System.Text; | |||
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[] Start2 = new byte[4] { 0, 0, 0, 1 }; | |||
@@ -44,6 +44,13 @@ namespace JT1078.Protocol.H264 | |||
/// 数据体 | |||
/// </summary> | |||
public byte[] RawData { get; set; } | |||
public string GetAVKey() | |||
{ | |||
return $"{SIM}_{LogicChannelNumber.ToString()}"; | |||
} | |||
[Obsolete("use GetAVKey")] | |||
public string GetKey() | |||
{ | |||
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> | |||
<returns></returns> | |||
</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)"> | |||
<summary> | |||
@@ -181,6 +189,17 @@ | |||
数据体 | |||
</summary> | |||
</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"> | |||
<summary> | |||
V - 2 - 固定为2 | |||
@@ -322,7 +341,7 @@ | |||
</member> | |||
<member name="P:JT1078.Protocol.JT1078Package.LastFrameInterval"> | |||
<summary> | |||
该帧与上一个关键帧之间的时间间隔,单位毫秒(ms), | |||
该帧与上一帧之间的时间间隔,单位毫秒(ms), | |||
当数据类型为非视频帧时,则没有该字段 | |||
</summary> | |||
</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 | |||
{ | |||
public class JT1078Package | |||
public class JT1078Package: IJT1078AVKey | |||
{ | |||
/// <summary> | |||
/// 帧头标识 | |||
@@ -74,7 +74,7 @@ namespace JT1078.Protocol | |||
/// </summary> | |||
public ushort LastIFrameInterval { get; set; } | |||
/// <summary> | |||
/// 该帧与上一个关键帧之间的时间间隔,单位毫秒(ms), | |||
/// 该帧与上一帧之间的时间间隔,单位毫秒(ms), | |||
/// 当数据类型为非视频帧时,则没有该字段 | |||
/// </summary> | |||
public ushort LastFrameInterval { get; set; } | |||
@@ -87,9 +87,15 @@ namespace JT1078.Protocol | |||
/// </summary> | |||
public byte[] Bodies{ get; set; } | |||
[Obsolete("use GetAVKey()")] | |||
public string GetKey() | |||
{ | |||
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.H264; | |||
using System.Net.WebSockets; | |||
using JT1078.Protocol.Enums; | |||
namespace JT1078.SignalR.Test.Services | |||
{ | |||
@@ -49,7 +50,7 @@ namespace JT1078.SignalR.Test.Services | |||
public List<byte[]> q = new List<byte[]>(); | |||
public void a() | |||
void Init() | |||
{ | |||
List<JT1078Package> packages = new List<JT1078Package>(); | |||
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>(); | |||
filter.Add(NalUnitType.SEI); | |||
filter.Add(NalUnitType.PPS); | |||
@@ -80,47 +79,35 @@ namespace JT1078.SignalR.Test.Services | |||
foreach (var package in packages) | |||
{ | |||
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>(); | |||
protected async override Task ExecuteAsync(CancellationToken stoppingToken) | |||
{ | |||
a(); | |||
Init(); | |||
while (!stoppingToken.IsCancellationRequested) | |||
{ | |||
try | |||
@@ -149,7 +136,7 @@ namespace JT1078.SignalR.Test.Services | |||
{ | |||
logger.LogError(ex,""); | |||
} | |||
await Task.Delay(80); | |||
await Task.Delay(60); | |||
} | |||
} | |||
} | |||