浏览代码

m3u8索引文件

tags/v1.1.0
waterliu99 4 年前
父节点
当前提交
e980d4258a
共有 8 个文件被更改,包括 331 次插入276 次删除
  1. +5
    -0
      src/JT1078.Hls.Test/JT1078.Hls.Test.csproj
  2. +0
    -25
      src/JT1078.Hls.Test/M3U8Config.cs
  3. +46
    -0
      src/JT1078.Hls.Test/M3U8_Test.cs
  4. +2
    -205
      src/JT1078.Hls.Test/TS_Package_Test.cs
  5. +0
    -44
      src/JT1078.Hls.Test/Ts_File_Manage.cs
  6. +159
    -0
      src/JT1078.Hls/M3U8FileManage.cs
  7. +45
    -0
      src/JT1078.Hls/Options/M3U8Option.cs
  8. +74
    -2
      src/JT1078.Hls/TSEncoder.cs

+ 5
- 0
src/JT1078.Hls.Test/JT1078.Hls.Test.csproj 查看文件

@@ -6,11 +6,16 @@
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<Compile Remove="StringBuilderPoolBenchmark.cs" />
</ItemGroup>

<ItemGroup>
<None Remove="H264\placeholder.txt" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="3.1.5" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />


+ 0
- 25
src/JT1078.Hls.Test/M3U8Config.cs 查看文件

@@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace JT1078.Hls.Test
{
/// <summary>
/// m3u8配置文件
/// </summary>
public class M3U8Config
{
/// <summary>
/// m3u8文件中包含的ts文件数
/// </summary>
public int TsFileCount { get; set; } = 10;
/// <summary>
/// 每个ts文件的最大时长
/// </summary>
public int TsFileMaxSecond { get; set; } = 10;
/// <summary>
/// m3u8文件中第一个ts文件序号
/// </summary>
public int FirstTsSerialNo { get; set; } = 0;
}
}

+ 46
- 0
src/JT1078.Hls.Test/M3U8_Test.cs 查看文件

@@ -0,0 +1,46 @@
using JT1078.Protocol;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Xunit;
using JT1078.Protocol.Extensions;

namespace JT1078.Hls.Test
{
public class M3U8_Test
{
/// <summary>
/// 生成m3u8索引文件
/// </summary>
[Fact]
public void Test4()
{
try
{
var hls_file_directory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "terminalno");
if (!File.Exists(hls_file_directory)) Directory.CreateDirectory(hls_file_directory);
var m3u8_filepath = Path.Combine(hls_file_directory, "live.m3u8");

TSEncoder tSEncoder = new TSEncoder(new M3U8FileManage (new Options.M3U8Option { HlsFileDirectory = hls_file_directory, M3U8Filepath = m3u8_filepath }) );
var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_3.txt"));
foreach (var line in lines)
{
var data = line.Split(',');
var bytes = data[6].ToHexBytes();
JT1078Package package = JT1078Serializer.Deserialize(bytes);
JT1078Package fullpackage = JT1078Serializer.Merge(package);
if (fullpackage != null)
{
tSEncoder.CreateM3U8File(fullpackage);
}
}
tSEncoder.AppendM3U8End();
}
catch (Exception ex)
{
Assert.Throws<Exception>(() => { });
}
}
}
}

+ 2
- 205
src/JT1078.Hls.Test/TS_Package_Test.cs 查看文件

@@ -81,7 +81,7 @@ namespace JT1078.Hls.Test
File.Delete(filepath);
var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_1.txt"));
fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write);
TSEncoder tSEncoder = new TSEncoder();
TSEncoder tSEncoder = new TSEncoder(new M3U8FileManage(new Options.M3U8Option { }));
foreach (var line in lines)
{
var data = line.Split(',');
@@ -128,7 +128,7 @@ namespace JT1078.Hls.Test
var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_3.txt"));
fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write);
bool isNeedFirstHeadler = true;
TSEncoder tSEncoder = new TSEncoder();
TSEncoder tSEncoder = new TSEncoder(new M3U8FileManage(new Options.M3U8Option { }));
foreach (var line in lines)
{
var data = line.Split(',');
@@ -168,209 +168,6 @@ namespace JT1078.Hls.Test
fileStream?.Dispose();
}
}
/// <summary>
/// 生成m3u8索引文件
/// </summary>
[Fact]
public void Test4()
{
try
{
ArrayPool<byte> arrayPool = ArrayPool<byte>.Create();
M3U8Config m3U8Config = new M3U8Config();
Ts_File_Manage ts_File_Manage = new Ts_File_Manage();
double file_real_second = m3U8Config.TsFileMaxSecond;

var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_3.txt"));
bool isNeedFirstHeadler = true;
TSEncoder tSEncoder = new TSEncoder();
ulong init_seconds = 0;
int duration = 0;
int accu_seconds = 0;

var hls_file_direcotry = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264");
var m3u8Filepath = Path.Combine(hls_file_direcotry, "index.m3u8");
AppendM3U8Start(m3u8Filepath, m3U8Config.TsFileMaxSecond, m3U8Config.FirstTsSerialNo);

var fileData= arrayPool.Rent(18888888);

int fileIndex = 0;
foreach (var line in lines)
{
var data = line.Split(',');
var bytes = data[6].ToHexBytes();
JT1078Package package = JT1078Serializer.Deserialize(bytes);
JT1078Package fullpackage = JT1078Serializer.Merge(package);
if (fullpackage != null)
{
if (accu_seconds / 1000>= m3U8Config.TsFileMaxSecond) {
//ecode_slice_header error 以非关键帧开始的报错信息
file_real_second = accu_seconds / 1000.0;//秒
//生成一个ts文件
var ts_name = $"{m3U8Config.FirstTsSerialNo}.ts";
var ts_filepath = Path.Combine(hls_file_direcotry, ts_name);
ts_File_Manage.CreateTsFile(ts_filepath, fileData.AsSpan().Slice(0, fileIndex).ToArray());
isNeedFirstHeadler = true;
arrayPool.Return(fileData);
fileData = arrayPool.Rent(18888888);
fileIndex = 0;
var media_sequence_no = m3U8Config.FirstTsSerialNo - m3U8Config.TsFileCount;
var del_ts_name=$"{media_sequence_no}.ts";
var del_ts_filepath = Path.Combine(hls_file_direcotry, del_ts_name);
//更新m3u8文件
UpdateM3U8File(m3u8Filepath, file_real_second, media_sequence_no+1, del_ts_filepath, del_ts_name,ts_name);

accu_seconds = 0;
m3U8Config.FirstTsSerialNo = m3U8Config.FirstTsSerialNo + 1;
}

if (init_seconds == 0)
{
init_seconds = fullpackage.Timestamp;
}
else {
duration =(int)( fullpackage.Timestamp - init_seconds);
init_seconds = fullpackage.Timestamp;
accu_seconds = Convert.ToInt32(accu_seconds) + duration;
}

if (isNeedFirstHeadler)
{
var sdt = tSEncoder.CreateSDT(fullpackage);
string sdtHEX = sdt.ToHexString();
sdt.CopyTo(fileData, fileIndex);
fileIndex = sdt.Length;
var pat = tSEncoder.CreatePAT(fullpackage);
string patHEX = pat.ToHexString();
pat.CopyTo(fileData, fileIndex);
fileIndex = fileIndex + pat.Length;
var pmt = tSEncoder.CreatePMT(fullpackage);
pmt.CopyTo(fileData, fileIndex);
fileIndex = fileIndex + pmt.Length;
var pes = tSEncoder.CreatePES(fullpackage, 18888);
pes.CopyTo(fileData, fileIndex);
fileIndex = fileIndex + pes.Length;
isNeedFirstHeadler = false;
}
else
{
var pes = tSEncoder.CreatePES(fullpackage, 18888);
pes.CopyTo(fileData, fileIndex);
fileIndex = fileIndex + pes.Length;
}
}
}
AppendM3U8End(m3u8Filepath);
}
catch (Exception ex)
{
Assert.Throws<Exception>(() => { });
}
}

private void CreateTsFile(string ts_filepath, byte[] data)
{
using (var fileStream = new FileStream(ts_filepath, FileMode.OpenOrCreate, FileAccess.Write))
{
fileStream.Write(data);
}
}

/// <summary>
///
/// </summary>
/// <param name="filepath"></param>
/// <param name="tsRealSecond"></param>
/// <param name="count"></param>
/// <param name="tsName"></param>
private void UpdateM3U8File(string m3u8_filepath,double tsRealSecond,int media_sequence_no, string del_ts_filepath,string del_ts_name, string ts_name) {
StringBuilder sb = new StringBuilder();
if (File.Exists(del_ts_filepath))
{
//删除最早一个ts文件
File.Delete(del_ts_filepath);
bool startAppendFileContent = true;
bool isFirstEXTINF = true;
using (StreamReader sr = new StreamReader(m3u8_filepath))
{
while (!sr.EndOfStream)
{
var text = sr.ReadLine();
if (text.Length == 0) continue;
if (text.StartsWith("#EXT-X-MEDIA-SEQUENCE"))
{
string media_sequence = $"#EXT-X-MEDIA-SEQUENCE:{media_sequence_no}";
sb.AppendLine(media_sequence);
continue;
}
if (text.StartsWith("#EXTINF") && isFirstEXTINF)
{
startAppendFileContent = false;
continue;
}
if (text.StartsWith(del_ts_name) && isFirstEXTINF)
{
isFirstEXTINF = false;
startAppendFileContent = true;
continue;
}
if (startAppendFileContent)
{
sb.AppendLine(text);
}
}
}
AppendTsToM3u8(m3u8_filepath, tsRealSecond, ts_name, sb, false);
}
else {
AppendTsToM3u8(m3u8_filepath, tsRealSecond, ts_name, sb);
}
}
/// <summary>
/// m3u8追加ts文件
/// </summary>
/// <param name="filepath"></param>
/// <param name="tsRealSecond"></param>
/// <param name="tsName"></param>
/// <param name="sb"></param>
private void AppendTsToM3u8(string m3u8_filepath, double tsRealSecond, string tsName, StringBuilder sb,bool isAppend=true) {
sb.AppendLine($"#EXTINF:{tsRealSecond},");//extra info,分片TS的信息,如时长,带宽等
sb.AppendLine($"{tsName}");//文件名
using (StreamWriter sw = new StreamWriter(m3u8_filepath, isAppend))
{
sw.WriteLine(sb);
}
}

private void AppendM3U8Start(string filepath,int fileMaxSecond,int firstTSSerialno) {
if(File.Exists(filepath)) File.Delete(filepath);
StringBuilder sb = new StringBuilder();
sb.AppendLine("#EXTM3U");//开始
sb.AppendLine("#EXT-X-VERSION:3");//版本号
sb.AppendLine("#EXT-X-ALLOW-CACHE:NO");//是否允许cache

sb.AppendLine($"#EXT-X-TARGETDURATION:{fileMaxSecond}");//每个分片TS的最大的时长
sb.AppendLine($"#EXT-X-MEDIA-SEQUENCE:{firstTSSerialno}");//第一个TS分片的序列号
using (StreamWriter sw = new StreamWriter(filepath,true))
{
sw.WriteLine(sb);
}
}
/// <summary>
/// 添加结束标识
/// </summary>
/// <param name="filepath"></param>
private void AppendM3U8End(string filepath) {
StringBuilder sb = new StringBuilder();
sb.AppendLine("#EXT-X-ENDLIST"); //m3u8文件结束符 表示视频已经结束 有这个标志同时也说明当前流是一个非直播流
//#EXT-X-PLAYLIST-TYPE:VOD/Live //VOD表示当前视频流不是一个直播流,而是点播流(也就是视频的全部ts文件已经生成)
using (StreamWriter sw = new StreamWriter(filepath,true))
{
sw.WriteLine(sb);
}
}

/// <summary>
///


+ 0
- 44
src/JT1078.Hls.Test/Ts_File_Manage.cs 查看文件

@@ -1,44 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace JT1078.Hls.Test
{
/// <summary>
/// ts文件管理
/// </summary>
public class Ts_File_Manage
{
/// <summary>
/// 创建ts文件
/// </summary>
/// <param name="ts_filepath">ts文件路径</param>
/// <param name="data">文件内容</param>
public void CreateTsFile(string ts_filepath, byte[] data)
{
DeleteTsFile(ts_filepath);
using (var fileStream = new FileStream(ts_filepath, FileMode.CreateNew, FileAccess.Write))
{
fileStream.Write(data);
}
}

/// <summary>
/// 删除ts文件
/// </summary>
/// <param name="ts_filepath">ts文件路径</param>
public void DeleteTsFile(string ts_filepath)
{
if (File.Exists(ts_filepath)) File.Delete(ts_filepath);
}
/// <summary>
/// ts文件是否存在
/// </summary>
/// <param name="ts_filepath"></param>
/// <returns></returns>
public bool ExistTsFile(string ts_filepath) {
return File.Exists(ts_filepath);
}
}
}

+ 159
- 0
src/JT1078.Hls/M3U8FileManage.cs 查看文件

@@ -0,0 +1,159 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Text;
using JT1078.Hls.Options;
using JT1078.Protocol;
using JT1078.Protocol.Extensions;

namespace JT1078.Hls
{
/// <summary>
/// m3u8文件管理
/// </summary>
public class M3U8FileManage
{
public readonly M3U8Option m3U8Option;

public M3U8FileManage(M3U8Option m3U8Option)
{
this.m3U8Option = m3U8Option;
AppendM3U8Start(m3U8Option.TsFileMaxSecond, m3U8Option.TsFileCount);
}

public void CreateM3U8File(JT1078Package fullpackage,byte[] data)
{
//ecode_slice_header error 以非关键帧开始的报错信息
//生成一个ts文件
var ts_name = $"{m3U8Option.TsFileCount}.ts";
var ts_filepath = Path.Combine(m3U8Option.HlsFileDirectory, ts_name);
CreateTsFile(ts_filepath, data);

var media_sequence_no = m3U8Option.TsFileCount - m3U8Option.TsFileCapacity;
var del_ts_name = $"{media_sequence_no}.ts";
//更新m3u8文件
UpdateM3U8File(m3U8Option.AccumulateSeconds, media_sequence_no + 1, del_ts_name, ts_name);

m3U8Option.IsNeedFirstHeadler = true;
m3U8Option.AccumulateSeconds = 0;
m3U8Option.TsFileCount = m3U8Option.TsFileCount + 1;
}

public void AppendM3U8Start(int fileMaxSecond, int firstTSSerialno)
{
if (File.Exists(m3U8Option.M3U8Filepath)) File.Delete(m3U8Option.M3U8Filepath);
StringBuilder sb = new StringBuilder();
sb.AppendLine("#EXTM3U");//开始
sb.AppendLine("#EXT-X-VERSION:3");//版本号
sb.AppendLine("#EXT-X-ALLOW-CACHE:NO");//是否允许cache

sb.AppendLine($"#EXT-X-TARGETDURATION:{fileMaxSecond}");//每个分片TS的最大的时长
sb.AppendLine($"#EXT-X-MEDIA-SEQUENCE:{firstTSSerialno}");//第一个TS分片的序列号
using (StreamWriter sw = new StreamWriter(m3U8Option.M3U8Filepath, true))
{
sw.WriteLine(sb);
}
}

/// <summary>
/// 添加结束标识
/// </summary>
/// <param name="filepath"></param>
public void AppendM3U8End()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("#EXT-X-ENDLIST"); //m3u8文件结束符 表示视频已经结束 有这个标志同时也说明当前流是一个非直播流
//#EXT-X-PLAYLIST-TYPE:VOD/Live //VOD表示当前视频流不是一个直播流,而是点播流(也就是视频的全部ts文件已经生成)
using (StreamWriter sw = new StreamWriter(m3U8Option.M3U8Filepath, true))
{
sw.WriteLine(sb);
}
}

/// <summary>
/// m3u8追加ts文件
/// </summary>
/// <param name="filepath"></param>
/// <param name="tsRealSecond"></param>
/// <param name="tsName"></param>
/// <param name="sb"></param>
public void AppendTsToM3u8(double tsRealSecond, string tsName, StringBuilder sb, bool isAppend = true)
{
sb.AppendLine($"#EXTINF:{tsRealSecond},");//extra info,分片TS的信息,如时长,带宽等
sb.AppendLine($"{tsName}");//文件名
using (StreamWriter sw = new StreamWriter(m3U8Option.M3U8Filepath, isAppend))
{
sw.WriteLine(sb);
}
}

/// <summary>
/// 更新m3u8文件
/// </summary>
/// <param name="filepath"></param>
/// <param name="tsRealSecond"></param>
/// <param name="count"></param>
/// <param name="tsName"></param>
public void UpdateM3U8File(double tsRealSecond, int media_sequence_no, string del_ts_name, string ts_name)
{
StringBuilder sb = new StringBuilder();
var del_ts_filepath = Path.Combine(m3U8Option.HlsFileDirectory, del_ts_name);
if (File.Exists(del_ts_filepath))
{
//删除最早一个ts文件
File.Delete(del_ts_filepath);
bool startAppendFileContent = true;
bool isFirstEXTINF = true;
using (StreamReader sr = new StreamReader(m3U8Option.M3U8Filepath))
{
while (!sr.EndOfStream)
{
var text = sr.ReadLine();
if (text.Length == 0) continue;
if (text.StartsWith("#EXT-X-MEDIA-SEQUENCE"))
{
string media_sequence = $"#EXT-X-MEDIA-SEQUENCE:{media_sequence_no}";
sb.AppendLine(media_sequence);
continue;
}
if (text.StartsWith("#EXTINF") && isFirstEXTINF)
{
startAppendFileContent = false;
continue;
}
if (text.StartsWith(del_ts_name) && isFirstEXTINF)
{
isFirstEXTINF = false;
startAppendFileContent = true;
continue;
}
if (startAppendFileContent)
{
sb.AppendLine(text);
}
}
}
AppendTsToM3u8( tsRealSecond, ts_name, sb, false);
}
else
{
AppendTsToM3u8(tsRealSecond, ts_name, sb);
}
}

/// <summary>
/// 创建ts文件
/// </summary>
/// <param name="ts_filepath">ts文件路径</param>
/// <param name="data">文件内容</param>
public void CreateTsFile(string ts_filepath, byte[] data)
{
File.Delete(ts_filepath);
using (var fileStream = new FileStream(ts_filepath, FileMode.CreateNew, FileAccess.Write))
{
fileStream.Write(data,0,data.Length);
}
}
}
}

+ 45
- 0
src/JT1078.Hls/Options/M3U8Option.cs 查看文件

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace JT1078.Hls.Options
{
/// <summary>
/// m3u8配置文件
/// </summary>
public class M3U8Option
{
/// <summary>
/// m3u8文件中默认包含的ts文件数
/// </summary>
public int TsFileCapacity { get; set; } = 10;
/// <summary>
/// 每个ts文件的最大时长
/// </summary>
public int TsFileMaxSecond { get; set; } = 10;
/// <summary>
/// 生成的ts的文件数
/// </summary>
public int TsFileCount { get; set; } = 0;
/// <summary>
/// 1078包的时间戳 毫秒
/// </summary>
public ulong TimestampMilliSecond { get; set; } = 0;
/// <summary>
/// 累计时长 如果大于文件时长就存储一个ts文件
/// </summary>
public double AccumulateSeconds { get; set; } = 0;
/// <summary>
/// 是否需要头部,每个ts文件都需要头部,重置为true
/// </summary>
public bool IsNeedFirstHeadler { get; set; } = true;
/// <summary>
/// m3u8文件
/// </summary>
public string M3U8Filepath { get; set; }
/// <summary>
/// hls文件路径
/// </summary>
public string HlsFileDirectory { get; set; }
}
}

+ 74
- 2
src/JT1078.Hls/TSEncoder.cs 查看文件

@@ -12,6 +12,8 @@ using System.Collections.Concurrent;
using System.Security.Cryptography;
using JT1078.Hls.Descriptors;
using JT1078.Protocol.Extensions;
using JT1078.Hls.Options;
using System.Buffers;

[assembly: InternalsVisibleTo("JT1078.Hls.Test")]

@@ -28,12 +30,82 @@ namespace JT1078.Hls
private const int FiexdTSLength = 188;
private const string ServiceProvider = "JTT1078";
private const string ServiceName = "Koike&TK";
private const int H264DefaultHZ = 90;
public TSEncoder()
private const int H264DefaultHZ = 90;


ArrayPool<byte> arrayPool = ArrayPool<byte>.Create();
byte[] fileData;
int fileIndex = 0;
private M3U8FileManage m3U8FileManage;
public TSEncoder(M3U8FileManage m3U8FileManage)
{
VideoCounter = new Dictionary<string, byte>(StringComparer.OrdinalIgnoreCase);
this.m3U8FileManage = m3U8FileManage;
fileData = arrayPool.Rent(2500000);
}
private Dictionary<string, byte> VideoCounter;
/// <summary>
/// 创建m3u8文件 和 ts文件
/// </summary>
/// <param name="jt1078Package"></param>
public void CreateM3U8File(JT1078Package jt1078Package) {
CombinedTSData(jt1078Package);
if (m3U8FileManage.m3U8Option.AccumulateSeconds >= m3U8FileManage.m3U8Option.TsFileMaxSecond) {
m3U8FileManage.CreateM3U8File(jt1078Package, fileData.AsSpan().Slice(0,fileIndex).ToArray());
arrayPool.Return(fileData);
fileData = arrayPool.Rent(2500000);
fileIndex = 0;
}
}
/// <summary>
/// m3u8文件 追加结束标识
/// </summary>
public void AppendM3U8End() {
m3U8FileManage.AppendM3U8End();
}
/// <summary>
/// 按 设定的时间(默认为10秒)切分ts文件
/// </summary>
/// <param name="jt1078Package"></param>
private void CombinedTSData(JT1078Package jt1078Package) {
if (m3U8FileManage.m3U8Option.TimestampMilliSecond == 0)
{
m3U8FileManage.m3U8Option.TimestampMilliSecond = jt1078Package.Timestamp;
}
else
{
int duration = (int)(jt1078Package.Timestamp - m3U8FileManage.m3U8Option.TimestampMilliSecond);
m3U8FileManage.m3U8Option.TimestampMilliSecond = jt1078Package.Timestamp;
m3U8FileManage.m3U8Option.AccumulateSeconds = m3U8FileManage.m3U8Option.AccumulateSeconds + duration / 1000.0;
}
if (m3U8FileManage.m3U8Option.IsNeedFirstHeadler)
{
var sdt = CreateSDT(jt1078Package);
sdt.CopyTo(fileData, fileIndex);
fileIndex = sdt.Length;

var pat = CreatePAT(jt1078Package);
pat.CopyTo(fileData, fileIndex);
fileIndex = fileIndex + pat.Length;

var pmt = CreatePMT(jt1078Package);
pmt.CopyTo(fileData, fileIndex);
fileIndex = fileIndex + pmt.Length;

var pes = CreatePES(jt1078Package, 18888);
pes.CopyTo(fileData, fileIndex);
fileIndex = fileIndex + pes.Length;

m3U8FileManage.m3U8Option.IsNeedFirstHeadler = false;
}
else
{
var pes = CreatePES(jt1078Package, 18888);
pes.CopyTo(fileData, fileIndex);
fileIndex = fileIndex + pes.Length;
}
}

//private ConcurrentDictionary<string, byte> AudioCounter = new ConcurrentDictionary<string, byte>();
public byte[] CreateSDT(JT1078Package jt1078Package, int minBufferSize = 188)
{


正在加载...
取消
保存