Pārlūkot izejas kodu

优化m3u8文件处理,待测试

tags/v1.1.0
waterliu99 pirms 4 gadiem
vecāks
revīzija
76c2e448d9
4 mainītis faili ar 107 papildinājumiem un 164 dzēšanām
  1. +1
    -0
      .gitignore
  2. +16
    -15
      src/JT1078.Hls.Test/M3U8_Test.cs
  3. +89
    -136
      src/JT1078.Hls/M3U8FileManage.cs
  4. +1
    -13
      src/JT1078.Hls/Options/M3U8Option.cs

+ 1
- 0
.gitignore Parādīt failu

@@ -332,3 +332,4 @@ ASALocalRun/
/src/JT1078.Flv.Test/H264/JT1078_4.txt
/src/JT1078.Flv.Test/H264/JT1078_5.txt
/doc/tools/FlvAnalyzer.exe
/doc/tools/EasyICE_2.7.0.2

+ 16
- 15
src/JT1078.Hls.Test/M3U8_Test.cs Parādīt failu

@@ -21,21 +21,22 @@ namespace JT1078.Hls.Test
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();
TSEncoder tSEncoder = new TSEncoder();
var m3u8Manage = new M3U8FileManage(new Options.M3U8Option { HlsFileDirectory = hls_file_directory, M3U8Filepath = m3u8_filepath }, tSEncoder);
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)
{
m3u8Manage.CreateTsData(fullpackage);
m3u8Manage.CreateM3U8File();
}
}
}
catch (Exception ex)
{


+ 89
- 136
src/JT1078.Hls/M3U8FileManage.cs Parādīt failu

@@ -3,7 +3,9 @@ using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using JT1078.Hls.MessagePack;
using JT1078.Hls.Options;
using JT1078.Protocol;
@@ -18,9 +20,9 @@ namespace JT1078.Hls
{
private TSEncoder tSEncoder;
public readonly M3U8Option m3U8Option;
ArrayPool<byte> arrayPool = ArrayPool<byte>.Create();
byte[] fileData;
int fileIndex = 0;
public ConcurrentQueue<TsFileInfo> tsFileInfoQueue = new ConcurrentQueue<TsFileInfo>();
public ConcurrentQueue<string> tsDelFileNameQueue = new ConcurrentQueue<string>();

ConcurrentDictionary<string, ulong> TsFirst1078PackageDic = new ConcurrentDictionary<string, ulong>();

@@ -28,37 +30,26 @@ namespace JT1078.Hls
{
this.tSEncoder = tSEncoder;
this.m3U8Option = m3U8Option;
fileData = arrayPool.Rent(2500000);
//AppendM3U8Start(m3U8Option.TsFileMaxSecond, m3U8Option.TsFileCount);
}
/// <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;
//}
}


private byte[] CreateTsData(JT1078Package jt1078Package, bool isNeedHeader,Span<byte> span) {
public void CreateTsData(JT1078Package jt1078Package) {
string tsFileDirectory = m3U8Option.HlsFileDirectory;
string tsNewFileName = $"{m3U8Option.TsFileCount}.ts";
string fileName= Path.Combine(tsFileDirectory, tsNewFileName);
var buff = TSArrayPool.Rent(jt1078Package.Bodies.Length + 2048);
TSMessagePackWriter tSMessagePackWriter = new TSMessagePackWriter(buff);
if (TsFirst1078PackageDic.TryGetValue(jt1078Package.SIM, out var firstTimespan))
{
var pes = tSEncoder.CreatePES(jt1078Package, 188);
tSMessagePackWriter.WriteArray(pes);
CreateTsFile(fileName, tSMessagePackWriter.FlushAndGetArray());
if ((jt1078Package.Timestamp - firstTimespan) > 10 * 1000)
{
//按设定的时间(默认为10秒)切分ts文件
TsFirst1078PackageDic.TryRemove(jt1078Package.SIM, out var _);
tsFileInfoQueue.Enqueue(new TsFileInfo { FileName= tsNewFileName, Duration= (jt1078Package.Timestamp - firstTimespan)/1000.0 });
m3U8Option.TsFileCount++;
}
var pes = tSEncoder.CreatePES(jt1078Package, 188);
tSMessagePackWriter.WriteArray(pes);
}
else {
var sdt = tSEncoder.CreateSDT(jt1078Package);
@@ -67,143 +58,105 @@ namespace JT1078.Hls
tSMessagePackWriter.WriteArray(pat);
var pmt = tSEncoder.CreatePMT(jt1078Package);
tSMessagePackWriter.WriteArray(pmt);
var pes = tSEncoder.CreatePES(jt1078Package, 188);
var pes = tSEncoder.CreatePES(jt1078Package);
tSMessagePackWriter.WriteArray(pes);
CreateTsFile(fileName, tSMessagePackWriter.FlushAndGetArray());
TsFirst1078PackageDic.TryAdd(jt1078Package.SIM, jt1078Package.Timestamp);
}
return buff;
}

public void CreateM3U8File(JT1078Package fullpackage,byte[] data)
public void CreateM3U8File()
{
//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))
if (tsFileInfoQueue.TryDequeue(out var tsFileInfo))
{
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))
string firstTsIndex = string.Empty;
StringBuilder sb = new StringBuilder();
List<string> fileHeader = new List<string>() {
"#EXTM3U",//开始
"#EXT-X-VERSION:3",//版本号
"#EXT-X-ALLOW-CACHE:NO",//是否允许cache
$"#EXT-X-TARGETDURATION:{m3U8Option.TsFileMaxSecond}",//第一个TS分片的序列号
$"#EXT-X-MEDIA-SEQUENCE:firstTsIndex"
};
Queue<string> fileBody = new Queue<string>();
if (!File.Exists(m3U8Option.M3U8Filepath)) File.Create(m3U8Option.M3U8Filepath);
using(FileStream fs=new FileStream(m3U8Option.M3U8Filepath,FileMode.Open,FileAccess.Read,FileShare.ReadWrite))
using (StreamReader sr = new StreamReader(fs))
{
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);
}
if (fileHeader.Contains(text)) continue;
if (text.StartsWith("#EXT-X-MEDIA-SEQUENCE")) continue;
fileBody.Enqueue(text);
}
if (fileBody.Count >= m3U8Option.TsFileCapacity * 2)
{
var deleteTsFileName_extraInfo = fileBody.Dequeue();
var deleteTsFileName = fileBody.Dequeue();
tsDelFileNameQueue.Enqueue(deleteTsFileName);

fileBody.Enqueue($"#EXTINF:{tsFileInfo.Duration},");
fileBody.Enqueue(tsFileInfo.FileName);

firstTsIndex = fileBody.ElementAt(1).Replace(".ts", "");
}
else {
fileBody.Enqueue($"#EXTINF:{tsFileInfo.Duration},");
fileBody.Enqueue(tsFileInfo.FileName);

firstTsIndex = fileBody.ElementAt(1).Replace(".ts", "");
}
fileHeader.ForEach((m) => {
if (m.Contains("firstTsIndex")) m = m.Replace("firstTsIndex", firstTsIndex);
sb.AppendLine(m);
});
fileBody.ToList().ForEach(m => {
sb.AppendLine(m);
});
}
using (FileStream fs = new FileStream(m3U8Option.M3U8Filepath, FileMode.Create,FileAccess.Write, FileShare.ReadWrite))
using (StreamWriter sw = new StreamWriter(fs))
{
sw.Write(sb.ToString());
}
AppendTsToM3u8( tsRealSecond, ts_name, sb, false);
}
else
{
AppendTsToM3u8(tsRealSecond, ts_name, sb);
}
}

/// <summary>
/// 创建ts文件
/// </summary>
/// <param name="ts_filepath">ts文件路径</param>
/// <param name="fileName">ts文件路径</param>
/// <param name="data">文件内容</param>
public void CreateTsFile(string ts_filepath, byte[] data)
private void CreateTsFile(string fileName, byte[] data)
{
File.Delete(ts_filepath);
using (var fileStream = new FileStream(ts_filepath, FileMode.CreateNew, FileAccess.Write))
var fileDirectory = fileName.Substring(0, fileName.LastIndexOf("\\"));
if (!Directory.Exists(fileDirectory)) Directory.CreateDirectory(fileDirectory);
using (var fileStream = new FileStream(fileName, FileMode.Append, FileAccess.Write,FileShare.ReadWrite, data.Length))
{
fileStream.Write(data,0,data.Length);
}
}

/// <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文件已经生成)
//}
}
/// <summary>
/// ts文件信息
/// </summary>
public class TsFileInfo {
public string FileName { get; set; }
public double Duration { get; set; }
}
}

+ 1
- 13
src/JT1078.Hls/Options/M3U8Option.cs Parādīt failu

@@ -18,22 +18,10 @@ namespace JT1078.Hls.Options
/// </summary>
public int TsFileMaxSecond { get; set; } = 10;
/// <summary>
/// 生成的ts文件数
/// 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; }


Notiek ielāde…
Atcelt
Saglabāt