@@ -6,11 +6,16 @@ | |||||
<IsPackable>false</IsPackable> | <IsPackable>false</IsPackable> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | |||||
<Compile Remove="StringBuilderPoolBenchmark.cs" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | <ItemGroup> | ||||
<None Remove="H264\placeholder.txt" /> | <None Remove="H264\placeholder.txt" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="3.1.5" /> | |||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> | <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> | ||||
<PackageReference Include="xunit" Version="2.4.0" /> | <PackageReference Include="xunit" Version="2.4.0" /> | ||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" /> | <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" /> | ||||
@@ -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; | |||||
} | |||||
} |
@@ -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>(() => { }); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -81,7 +81,7 @@ namespace JT1078.Hls.Test | |||||
File.Delete(filepath); | File.Delete(filepath); | ||||
var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_1.txt")); | var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_1.txt")); | ||||
fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | 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) | foreach (var line in lines) | ||||
{ | { | ||||
var data = line.Split(','); | 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")); | var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_3.txt")); | ||||
fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | ||||
bool isNeedFirstHeadler = true; | bool isNeedFirstHeadler = true; | ||||
TSEncoder tSEncoder = new TSEncoder(); | |||||
TSEncoder tSEncoder = new TSEncoder(new M3U8FileManage(new Options.M3U8Option { })); | |||||
foreach (var line in lines) | foreach (var line in lines) | ||||
{ | { | ||||
var data = line.Split(','); | var data = line.Split(','); | ||||
@@ -168,209 +168,6 @@ namespace JT1078.Hls.Test | |||||
fileStream?.Dispose(); | 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> | /// <summary> | ||||
/// | /// | ||||
@@ -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); | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -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; } | |||||
} | |||||
} |
@@ -12,6 +12,8 @@ using System.Collections.Concurrent; | |||||
using System.Security.Cryptography; | using System.Security.Cryptography; | ||||
using JT1078.Hls.Descriptors; | using JT1078.Hls.Descriptors; | ||||
using JT1078.Protocol.Extensions; | using JT1078.Protocol.Extensions; | ||||
using JT1078.Hls.Options; | |||||
using System.Buffers; | |||||
[assembly: InternalsVisibleTo("JT1078.Hls.Test")] | [assembly: InternalsVisibleTo("JT1078.Hls.Test")] | ||||
@@ -28,12 +30,82 @@ namespace JT1078.Hls | |||||
private const int FiexdTSLength = 188; | private const int FiexdTSLength = 188; | ||||
private const string ServiceProvider = "JTT1078"; | private const string ServiceProvider = "JTT1078"; | ||||
private const string ServiceName = "Koike&TK"; | 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); | VideoCounter = new Dictionary<string, byte>(StringComparer.OrdinalIgnoreCase); | ||||
this.m3U8FileManage = m3U8FileManage; | |||||
fileData = arrayPool.Rent(2500000); | |||||
} | } | ||||
private Dictionary<string, byte> VideoCounter; | 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>(); | //private ConcurrentDictionary<string, byte> AudioCounter = new ConcurrentDictionary<string, byte>(); | ||||
public byte[] CreateSDT(JT1078Package jt1078Package, int minBufferSize = 188) | public byte[] CreateSDT(JT1078Package jt1078Package, int minBufferSize = 188) | ||||
{ | { | ||||