瀏覽代碼

将1078转fmp4_11

master
SmallChi(Koike) 4 年之前
父節點
當前提交
97b8832127
共有 13 個文件被更改,包括 89 次插入66 次删除
  1. +1
    -1
      src/JT1078.AV.Benchmark/JT1078FlvEncoderContext.cs
  2. +10
    -10
      src/JT1078.FMp4.Test/H264/index.html
  3. +10
    -9
      src/JT1078.FMp4.Test/JT1078ToFMp4Box_Test.cs
  4. +2
    -0
      src/JT1078.FMp4/Boxs/SegmentIndexBox.cs
  5. +6
    -4
      src/JT1078.FMp4/Boxs/TrackRunBox.cs
  6. +20
    -26
      src/JT1078.FMp4/FMp4Encoder.cs
  7. +6
    -1
      src/JT1078.FMp4/JT1078.FMp4.xml
  8. +4
    -4
      src/JT1078.Flv/FlvEncoder.cs
  9. +1
    -1
      src/JT1078.Protocol.Benchmark/JT1078FlvEncoderContext.cs
  10. +5
    -5
      src/JT1078.Protocol.Test/H264/H264DecoderTest.cs
  11. +1
    -1
      src/JT1078.Protocol.Test/H264/NALUHeaderTest.cs
  12. +20
    -3
      src/JT1078.Protocol/H264/NALUHeader.cs
  13. +3
    -1
      src/JT1078.SignalR.Test/Services/ToWebSocketService.cs

+ 1
- 1
src/JT1078.AV.Benchmark/JT1078FlvEncoderContext.cs 查看文件

@@ -42,7 +42,7 @@ namespace JT1078.AV.Benchmark
Package = JT1078Serializer.Merge(package);
}
H264NALUs = h264Decoder.ParseNALU(Package);
SPSNALu = H264NALUs.FirstOrDefault(f => f.NALUHeader.NalUnitType == 7);
SPSNALu = H264NALUs.FirstOrDefault(f => f.NALUHeader.NalUnitType == NalUnitType.SPS);
SPSNALu.RawData = h264Decoder.DiscardEmulationPreventionBytes(SPSNALu.RawData);
}



+ 10
- 10
src/JT1078.FMp4.Test/H264/index.html 查看文件

@@ -96,17 +96,17 @@
// otherwise insert it to queue
var memview = new Uint8Array(arr);
if (verbose) { console.log("got", arr.byteLength, "bytes. Values=", memview[0], memview[1], memview[2], memview[3], memview[4]); }
//res = getBox(memview, 0);
//main_length = res[0]; name = res[1]; // this boxes length and name
//if ((name == "ftyp") && (pass == 0)) {
res = getBox(memview, 0);
main_length = res[0]; name = res[1]; // this boxes length and name
// if ((name == "ftyp") && (pass == 0)) {
// pass = pass + 1;
// console.log("got ftyp");
//}
//else if ((name == "moov") && (pass == 1)) {
// }
// else if ((name == "moov") && (pass == 1)) {
// pass = pass + 1;
// console.log("got moov");
//}
//else if ((name == "moof") && (pass == 2)) {
// }
// else if ((name == "moof") && (pass == 2)) {
// if (hasFirstSampleFlag(memview)) {
// pass = pass + 1;
// console.log("got that special moof");
@@ -114,10 +114,10 @@
// else {
// return;
// }
//}
//else if (pass < 3) {
// }
// else if (pass < 3) {
// return;
//}
// }
// keep the latency to minimum
let latest = stream_live.duration;
if ((stream_live.duration >= buffering_sec) &&


+ 10
- 9
src/JT1078.FMp4.Test/JT1078ToFMp4Box_Test.cs 查看文件

@@ -24,10 +24,10 @@ namespace JT1078.FMp4.Test
var jT1078Package = ParseNALUTest();
H264Decoder decoder = new H264Decoder();
var nalus = decoder.ParseNALU(jT1078Package);
var spsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == 7);
var spsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.SPS);
//SPS
spsNALU.RawData = decoder.DiscardEmulationPreventionBytes(spsNALU.RawData);
var ppsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == 8);
var ppsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.PPS);
ppsNALU.RawData = decoder.DiscardEmulationPreventionBytes(ppsNALU.RawData);
//ftyp
FileTypeBox fileTypeBox = new FileTypeBox();
@@ -176,10 +176,10 @@ namespace JT1078.FMp4.Test
var jT1078Package = ParseNALUTest();
H264Decoder decoder = new H264Decoder();
var nalus = decoder.ParseNALU(jT1078Package);
var spsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == 7);
var spsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.SPS);
//SPS
spsNALU.RawData = decoder.DiscardEmulationPreventionBytes(spsNALU.RawData);
var ppsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == 8);
var ppsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.PPS);
ppsNALU.RawData = decoder.DiscardEmulationPreventionBytes(ppsNALU.RawData);
FMp4MessagePackWriter writer = new FMp4MessagePackWriter(new byte[65535]);
//ftyp
@@ -324,10 +324,10 @@ namespace JT1078.FMp4.Test
using var fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write);
var jT1078Package = packages.FirstOrDefault();
var nalus = decoder.ParseNALU(jT1078Package);
var spsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == 7);
var spsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.SPS);
//SPS
spsNALU.RawData = decoder.DiscardEmulationPreventionBytes(spsNALU.RawData);
var ppsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == 8);
var ppsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.PPS);
ppsNALU.RawData = decoder.DiscardEmulationPreventionBytes(ppsNALU.RawData);
ExpGolombReader h264GolombReader = new ExpGolombReader(spsNALU.RawData);
var spsInfo = h264GolombReader.ReadSPS();
@@ -455,15 +455,16 @@ namespace JT1078.FMp4.Test
{
var otherStypBuffer = fMp4Encoder.EncoderStypBox();
fileStream.Write(otherStypBuffer);
var otherSidxBuffer = fMp4Encoder.EncoderSidxBox(package.Timestamp, package.LastIFrameInterval);
fileStream.Write(otherSidxBuffer);
var otherNalus = h264Decoder.ParseNALU(package);
var flag = package.Label3.DataType == Protocol.Enums.JT1078DataType.视频I帧 ? 1u : 0u;

var otherMoofBuffer = fMp4Encoder.EncoderMoofBox(otherNalus, package.Bodies.Length, package.Timestamp, package.LastIFrameInterval, flag);
var otherMdatBuffer = fMp4Encoder.EncoderMdatBox(otherNalus, package.Bodies.Length);
var otherSidxBuffer = fMp4Encoder.EncoderSidxBox(otherMoofBuffer.Length + otherMdatBuffer.Length, package.Timestamp, package.LastIFrameInterval);

fileStream.Write(otherSidxBuffer);
fileStream.Write(otherMoofBuffer);
fileStream.Write(otherMdatBuffer);

}
fileStream.Close();
}


+ 2
- 0
src/JT1078.FMp4/Boxs/SegmentIndexBox.cs 查看文件

@@ -28,6 +28,7 @@ namespace JT1078.FMp4
/// </summary>
public uint Timescale { get; set; } = 1000;
/// <summary>
/// pts
/// if(version==0)
/// version==0 32 bit
/// version>0 64 bit
@@ -100,6 +101,7 @@ namespace JT1078.FMp4
public bool ReferenceType { get; set; } = false;
/// <summary>
/// 4byte 32 - 31
/// ReferencedSize=(moof size) + (mdat size)
/// </summary>
public uint ReferencedSize { get; set; } = 0;
/// <summary>


+ 6
- 4
src/JT1078.FMp4/Boxs/TrackRunBox.cs 查看文件

@@ -24,6 +24,7 @@ namespace JT1078.FMp4
/// 0x000200-0010 0000 0000: 每个 sample 有自己的 sample_size,否则使用默认的。
/// 0x000400-0100 0000 0000: 对每个 sample 使用自己的 flags。否则,使用默认的。
/// 0x000800-1000 0000 0000: 每个 sample 都有自己的 cts 值
/// 0x000f01-1111 0000 0001
/// </summary>
/// <param name="version"></param>
/// <param name="flags"></param>
@@ -101,11 +102,11 @@ namespace JT1078.FMp4
{
if (Version == 0)
{
writer.WriteUInt32(trun.SampleCompositionTimeOffset);
writer.WriteUInt32((uint)trun.SampleCompositionTimeOffset);
}
else
{
writer.WriteInt32(trun.SignedSampleCompositionTimeOffset);
writer.WriteInt32((int)trun.SampleCompositionTimeOffset);
}
}
}
@@ -120,9 +121,10 @@ namespace JT1078.FMp4
public uint SampleFlags { get; set; }
/// <summary>
/// version == 0
/// 0:uint
/// >0:int
/// </summary>
public uint SampleCompositionTimeOffset { get; set; }
public int SignedSampleCompositionTimeOffset { get; set; }
public long SampleCompositionTimeOffset { get; set; }
}
}
}

+ 20
- 26
src/JT1078.FMp4/FMp4Encoder.cs 查看文件

@@ -31,6 +31,7 @@ namespace JT1078.FMp4
public class FMp4Encoder
{
readonly H264Decoder h264Decoder;

/// <summary>
///
/// </summary>
@@ -111,7 +112,7 @@ namespace JT1078.FMp4
/// 编码sidx盒子
/// </summary>
/// <returns></returns>
public byte[] EncoderSidxBox(ulong timestamp, uint frameInterval)
public byte[] EncoderSidxBox(int moofAndMdatLength, ulong timestamp, uint frameInterval)
{
byte[] buffer = FMp4ArrayPool.Rent(4096);
FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer);
@@ -119,11 +120,12 @@ namespace JT1078.FMp4
{
SegmentIndexBox segmentIndexBox = new SegmentIndexBox(1);
segmentIndexBox.ReferenceID = 1;
segmentIndexBox.EarliestPresentationTime = timestamp;
segmentIndexBox.EarliestPresentationTime = timestamp*1000;
segmentIndexBox.SegmentIndexs = new List<SegmentIndexBox.SegmentIndex>()
{
new SegmentIndexBox.SegmentIndex
{
ReferencedSize=(uint)moofAndMdatLength,
SubsegmentDuration=frameInterval
}
};
@@ -147,10 +149,10 @@ namespace JT1078.FMp4
FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer);
try
{
var spsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == 7);
var spsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.SPS);
//SPS
spsNALU.RawData = h264Decoder.DiscardEmulationPreventionBytes(spsNALU.RawData);
var ppsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == 8);
var ppsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.PPS);
ppsNALU.RawData = h264Decoder.DiscardEmulationPreventionBytes(ppsNALU.RawData);
ExpGolombReader h264GolombReader = new ExpGolombReader(spsNALU.RawData);
var spsInfo = h264GolombReader.ReadSPS();
@@ -242,37 +244,29 @@ namespace JT1078.FMp4
//0x02 分段
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox = new TrackFragmentHeaderBox(0x20038);
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.TrackID = 1;
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleDuration = 48000;
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleDuration = frameInterval;
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleSize = (uint)naluLength;
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleFlags = 0x1010000;
movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox = new TrackFragmentBaseMediaDecodeTimeBox();
movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox.BaseMediaDecodeTime = timestamp*1000;
//trun
//0x39 写文件
//0x02 分段
//0x205
//uint flag = 0x000200 | 0x000800 | 0x000400 | 0x000100;
uint flag = 4u;
if (!first)
{
//sdtp.IsLeading = 1;
//flag = 4u;
movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox.BaseMediaDecodeTime = 0;
movieFragmentBox.TrackFragmentBox.TrackRunBox = new TrackRunBox(flags: 5);
first = true;
}
else
{
//flag = 0x000400;
movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox.BaseMediaDecodeTime = timestamp*1000;
movieFragmentBox.TrackFragmentBox.TrackRunBox = new TrackRunBox(flags: 5);
}
movieFragmentBox.TrackFragmentBox.TrackRunBox = new TrackRunBox();
movieFragmentBox.TrackFragmentBox.TrackRunBox.FirstSampleFlags = 33554432;
movieFragmentBox.TrackFragmentBox.TrackRunBox.TrackRunInfos = new List<TrackRunBox.TrackRunInfo>();
if (frameIntervalCache == 0)
{
frameIntervalCache += frameInterval;
}
movieFragmentBox.TrackFragmentBox.TrackRunBox.TrackRunInfos.Add(new TrackRunBox.TrackRunInfo()
{
SampleDuration= frameInterval,
SampleDuration= frameIntervalCache,
SampleSize = (uint)naluLength,
SampleCompositionTimeOffset = frameInterval,
SampleFlags = flag
SampleCompositionTimeOffset = (long)timestamp ,
SampleFlags = movieFragmentBox.TrackFragmentBox.TrackRunBox.Flags
});
movieFragmentBox.ToBuffer(ref writer);
var data = writer.FlushAndGetArray();
@@ -318,10 +312,10 @@ namespace JT1078.FMp4
try
{
var nalus = h264Decoder.ParseNALU(package);
var spsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == 7);
var spsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.SPS);
//SPS
spsNALU.RawData = h264Decoder.DiscardEmulationPreventionBytes(spsNALU.RawData);
var ppsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == 8);
var ppsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.PPS);
ppsNALU.RawData = h264Decoder.DiscardEmulationPreventionBytes(ppsNALU.RawData);
ExpGolombReader h264GolombReader = new ExpGolombReader(spsNALU.RawData);
var spsInfo = h264GolombReader.ReadSPS();
@@ -405,7 +399,7 @@ namespace JT1078.FMp4
}

uint sn = 1;
uint frameIntervalCache = 0;
bool first = false;

/// <summary>


+ 6
- 1
src/JT1078.FMp4/JT1078.FMp4.xml 查看文件

@@ -700,6 +700,7 @@
</member>
<member name="P:JT1078.FMp4.SegmentIndexBox.EarliestPresentationTime">
<summary>
pts
if(version==0)
version==0 32 bit
version>0 64 bit
@@ -723,6 +724,7 @@
<member name="P:JT1078.FMp4.SegmentIndexBox.SegmentIndex.ReferencedSize">
<summary>
4byte 32 - 31
ReferencedSize=(moof size) + (mdat size)
</summary>
</member>
<member name="P:JT1078.FMp4.SegmentIndexBox.SegmentIndex.SubsegmentDuration">
@@ -1062,6 +1064,7 @@
0x000200-0010 0000 0000: 每个 sample 有自己的 sample_size,否则使用默认的。
0x000400-0100 0000 0000: 对每个 sample 使用自己的 flags。否则,使用默认的。
0x000800-1000 0000 0000: 每个 sample 都有自己的 cts 值
0x000f01-1111 0000 0001
</summary>
<param name="version"></param>
<param name="flags"></param>
@@ -1092,6 +1095,8 @@
<member name="P:JT1078.FMp4.TrackRunBox.TrackRunInfo.SampleCompositionTimeOffset">
<summary>
version == 0
0:uint
>0:int
</summary>
</member>
<member name="T:JT1078.FMp4.URIBox">
@@ -1330,7 +1335,7 @@
</summary>
<returns></returns>
</member>
<member name="M:JT1078.FMp4.FMp4Encoder.EncoderSidxBox(System.UInt64,System.UInt32)">
<member name="M:JT1078.FMp4.FMp4Encoder.EncoderSidxBox(System.Int32,System.UInt64,System.UInt32)">
<summary>
编码sidx盒子
</summary>


+ 4
- 4
src/JT1078.Flv/FlvEncoder.cs 查看文件

@@ -207,9 +207,9 @@ namespace JT1078.Flv
var nalus = h264Decoder.ParseNALU(package);
if (nalus != null && nalus.Count > 0)
{
var sei = nalus.FirstOrDefault(x => x.NALUHeader.NalUnitType == 6);
var sps = nalus.FirstOrDefault(x => x.NALUHeader.NalUnitType == 7);
var pps = nalus.FirstOrDefault(x => x.NALUHeader.NalUnitType == 8);
var sei = nalus.FirstOrDefault(x => x.NALUHeader.NalUnitType == NalUnitType.SEI);
var sps = nalus.FirstOrDefault(x => x.NALUHeader.NalUnitType == NalUnitType.SPS);
var pps = nalus.FirstOrDefault(x => x.NALUHeader.NalUnitType == NalUnitType.PPS);
nalus.Remove(sps);
nalus.Remove(pps);
nalus.Remove(sei);
@@ -297,7 +297,7 @@ namespace JT1078.Flv
//1: keyframe (for AVC, a seekable frame) —— 即H.264的IDR帧;
//2: inter frame(for AVC, a non - seekable frame) —— H.264的普通I帧;
//ref:https://www.cnblogs.com/chyingp/p/flv-getting-started.html
if (nALU.NALUHeader.NalUnitType == 5)
if (nALU.NALUHeader.NalUnitType == NalUnitType.IDR)
{
flvTags.VideoTagsData.FrameType = FrameType.KeyFrame;
}


+ 1
- 1
src/JT1078.Protocol.Benchmark/JT1078FlvEncoderContext.cs 查看文件

@@ -41,7 +41,7 @@ namespace JT1078.Flv.Benchmark
Package = JT1078Serializer.Merge(package);
}
H264NALUs = h264Decoder.ParseNALU(Package);
SPSNALu = H264NALUs.FirstOrDefault(f => f.NALUHeader.NalUnitType == 7);
SPSNALu = H264NALUs.FirstOrDefault(f => f.NALUHeader.NalUnitType == NalUnitType.SPS);
SPSNALu.RawData = h264Decoder.DiscardEmulationPreventionBytes(SPSNALu.RawData);
}



+ 5
- 5
src/JT1078.Protocol.Test/H264/H264DecoderTest.cs 查看文件

@@ -26,7 +26,7 @@ namespace JT1078.Protocol.Test.H264
var nalu = nalus[0];
Assert.Equal(0, nalu.NALUHeader.ForbiddenZeroBit);
Assert.Equal(3, nalu.NALUHeader.NalRefIdc);
Assert.Equal(1, nalu.NALUHeader.NalUnitType);
Assert.Equal(NalUnitType.SLICE, nalu.NALUHeader.NalUnitType);
}

[Fact]
@@ -48,7 +48,7 @@ namespace JT1078.Protocol.Test.H264
Assert.Equal(4, nalus.Count);

//SPS -> 7
var spsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == 7);
var spsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.SPS);
Assert.NotNull(spsNALU);
spsNALU.RawData = decoder.DiscardEmulationPreventionBytes(spsNALU.RawData);
//"Z00AFJWoWCWQ"
@@ -63,15 +63,15 @@ namespace JT1078.Protocol.Test.H264
Assert.Equal(352, spsInfo.width);

//PPS -> 8
var ppsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == 8);
var ppsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.PPS);
Assert.NotNull(ppsNALU);

//IDR -> 5 关键帧
var idrNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == 5);
var idrNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.IDR);
Assert.NotNull(idrNALU);

//SEI -> 6
var seiNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == 6);
var seiNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.SEI);
Assert.NotNull(seiNALU);
}



+ 1
- 1
src/JT1078.Protocol.Test/H264/NALUHeaderTest.cs 查看文件

@@ -14,7 +14,7 @@ namespace JT1078.Flv.Test.H264
NALUHeader header = new NALUHeader(0xc0);
Assert.Equal(1, header.ForbiddenZeroBit);
Assert.Equal(2, header.NalRefIdc);
Assert.Equal(0, header.NalUnitType);
Assert.Equal(NalUnitType.None, header.NalUnitType);
}
}
}

+ 20
- 3
src/JT1078.Protocol/H264/NALUHeader.cs 查看文件

@@ -10,16 +10,33 @@ namespace JT1078.Protocol.H264
{
ForbiddenZeroBit = (value & 0x80) >> 7;
NalRefIdc = (value & 0x60) >> 5;
NalUnitType = value & 0x1f;
NalUnitType = (NalUnitType)(value & 0x1f);
}
public NALUHeader(ReadOnlySpan<byte> value)
{
ForbiddenZeroBit = (value[0] & 0x80) >> 7;
NalRefIdc = (value[0] & 0x60) >> 5;
NalUnitType = value[0] & 0x1f;
NalUnitType = (NalUnitType)(value[0] & 0x1f);
}
public int ForbiddenZeroBit { get; set; }
public int NalRefIdc { get; set; }
public int NalUnitType { get; set; }
public NalUnitType NalUnitType { get; set; }
}

public enum NalUnitType : int
{
None=0,
SLICE = 1,
DPA = 2,
DPB = 3,
DPC = 4,
IDR = 5,
SEI = 6,
SPS = 7,
PPS = 8,
AUD = 9,
EOSEQ = 10,
EOSTREAM = 11,
FILL = 12,
}
}

+ 3
- 1
src/JT1078.SignalR.Test/Services/ToWebSocketService.cs 查看文件

@@ -17,6 +17,7 @@ using JT1078.Protocol;
using System.IO;
using JT1078.Protocol.Extensions;
using JT1078.Protocol.H264;
using System.Net.WebSockets;

namespace JT1078.SignalR.Test.Services
{
@@ -78,7 +79,7 @@ namespace JT1078.SignalR.Test.Services
//q.Enqueue(moov);
first.Add(moov);
q.Add(first.SelectMany(s=>s).ToArray());
List<int> filter = new List<int>() { 6,7,8};
List<NalUnitType> filter = new List<NalUnitType>() { NalUnitType.SEI,NalUnitType.SPS,NalUnitType.PPS};
foreach (var package in packages)
{
List<byte[]> other = new List<byte[]>();
@@ -97,6 +98,7 @@ namespace JT1078.SignalR.Test.Services
}
}


public Dictionary<string,int> flag = new Dictionary<string, int>();

protected async override Task ExecuteAsync(CancellationToken stoppingToken)


Loading…
取消
儲存