mirror of
https://github.com/BililiveRecorder/BililiveRecorder.git
synced 2024-11-16 11:42:22 +08:00
FLV: Performance improvements
(I guess)
This commit is contained in:
parent
5c9706e827
commit
93781b2a56
|
@ -17,6 +17,7 @@
|
||||||
<PackageReference Include="Polly" Version="7.2.1" />
|
<PackageReference Include="Polly" Version="7.2.1" />
|
||||||
<PackageReference Include="Polly.Caching.Memory" Version="3.0.2" />
|
<PackageReference Include="Polly.Caching.Memory" Version="3.0.2" />
|
||||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||||
|
<PackageReference Include="StructLinq" Version="0.26.0" />
|
||||||
<PackageReference Include="System.IO.Pipelines" Version="5.0.1" />
|
<PackageReference Include="System.IO.Pipelines" Version="5.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using BililiveRecorder.Flv.Pipeline;
|
using BililiveRecorder.Flv.Pipeline;
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
|
||||||
namespace BililiveRecorder.Core.ProcessingRules
|
namespace BililiveRecorder.Core.ProcessingRules
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,6 +4,8 @@ using System.Linq;
|
||||||
using BililiveRecorder.Core.Event;
|
using BililiveRecorder.Core.Event;
|
||||||
using BililiveRecorder.Flv;
|
using BililiveRecorder.Flv;
|
||||||
using BililiveRecorder.Flv.Pipeline;
|
using BililiveRecorder.Flv.Pipeline;
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
using StructLinq;
|
||||||
|
|
||||||
namespace BililiveRecorder.Core.ProcessingRules
|
namespace BililiveRecorder.Core.ProcessingRules
|
||||||
{
|
{
|
||||||
|
@ -32,10 +34,20 @@ namespace BililiveRecorder.Core.ProcessingRules
|
||||||
{
|
{
|
||||||
var e = new RecordingStatsEventArgs();
|
var e = new RecordingStatsEventArgs();
|
||||||
|
|
||||||
e.TotalInputVideoByteCount = this.TotalInputVideoByteCount += e.InputVideoByteCount =
|
{
|
||||||
context.Actions.Where(x => x is PipelineDataAction).Cast<PipelineDataAction>().Sum(data => data.Tags.Where(x => x.Type == TagType.Video).Sum(x => x.Size + (11 + 4)));
|
static IEnumerable<PipelineDataAction> FilterDataActions(IEnumerable<PipelineAction> actions)
|
||||||
e.TotalInputAudioByteCount = this.TotalInputAudioByteCount += e.InputAudioByteCount =
|
{
|
||||||
context.Actions.Where(x => x is PipelineDataAction).Cast<PipelineDataAction>().Sum(data => data.Tags.Where(x => x.Type == TagType.Audio).Sum(x => x.Size + (11 + 4)));
|
foreach (var action in actions)
|
||||||
|
if (action is PipelineDataAction dataAction)
|
||||||
|
yield return dataAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.TotalInputVideoByteCount = this.TotalInputVideoByteCount += e.InputVideoByteCount =
|
||||||
|
FilterDataActions(context.Actions).ToStructEnumerable().Sum(ref LinqFunctions.SumSizeOfVideoData, x => x, x => x);
|
||||||
|
|
||||||
|
e.TotalInputAudioByteCount = this.TotalInputAudioByteCount += e.InputAudioByteCount =
|
||||||
|
FilterDataActions(context.Actions).ToStructEnumerable().Sum(ref LinqFunctions.SumSizeOfAudioData, x => x, x => x);
|
||||||
|
}
|
||||||
|
|
||||||
next();
|
next();
|
||||||
|
|
||||||
|
@ -81,10 +93,17 @@ namespace BililiveRecorder.Core.ProcessingRules
|
||||||
{
|
{
|
||||||
if (dataActions.Count > 0)
|
if (dataActions.Count > 0)
|
||||||
{
|
{
|
||||||
e.TotalOutputVideoFrameCount = this.TotalOutputVideoFrameCount += e.OutputVideoFrameCount = dataActions.Sum(x => x.Tags.Count(x => x.Type == TagType.Video));
|
e.TotalOutputVideoFrameCount = this.TotalOutputVideoFrameCount += e.OutputVideoFrameCount =
|
||||||
e.TotalOutputAudioFrameCount = this.TotalOutputAudioFrameCount += e.OutputAudioFrameCount = dataActions.Sum(x => x.Tags.Count(x => x.Type == TagType.Audio));
|
dataActions.ToStructEnumerable().Sum(ref LinqFunctions.CountVideoTags, x => x, x => x);
|
||||||
e.TotalOutputVideoByteCount = this.TotalOutputVideoByteCount += e.OutputVideoByteCount = dataActions.Sum(x => x.Tags.Where(x => x.Type == TagType.Video).Sum(x => (x.Nalus == null ? x.Size : (5 + x.Nalus.Sum(n => n.FullSize + 4))) + (11 + 4)));
|
|
||||||
e.TotalOutputAudioByteCount = this.TotalOutputAudioByteCount += e.OutputAudioByteCount = dataActions.Sum(x => x.Tags.Where(x => x.Type == TagType.Audio).Sum(x => x.Size + (11 + 4)));
|
e.TotalOutputAudioFrameCount = this.TotalOutputAudioFrameCount += e.OutputAudioFrameCount =
|
||||||
|
dataActions.ToStructEnumerable().Sum(ref LinqFunctions.CountAudioTags, x => x, x => x);
|
||||||
|
|
||||||
|
e.TotalOutputVideoByteCount = this.TotalOutputVideoByteCount += e.OutputVideoByteCount =
|
||||||
|
dataActions.ToStructEnumerable().Sum(ref LinqFunctions.SumSizeOfVideoDataByNalu, x => x, x => x);
|
||||||
|
|
||||||
|
e.TotalOutputAudioByteCount = this.TotalOutputAudioByteCount += e.OutputAudioByteCount =
|
||||||
|
dataActions.ToStructEnumerable().Sum(ref LinqFunctions.SumSizeOfAudioData, x => x, x => x);
|
||||||
|
|
||||||
e.CurrnetFileSize = this.CurrnetFileSize += e.OutputVideoByteCount + e.OutputAudioByteCount;
|
e.CurrnetFileSize = this.CurrnetFileSize += e.OutputVideoByteCount + e.OutputAudioByteCount;
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ using BililiveRecorder.Core.ProcessingRules;
|
||||||
using BililiveRecorder.Flv;
|
using BililiveRecorder.Flv;
|
||||||
using BililiveRecorder.Flv.Amf;
|
using BililiveRecorder.Flv.Amf;
|
||||||
using BililiveRecorder.Flv.Pipeline;
|
using BililiveRecorder.Flv.Pipeline;
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace BililiveRecorder.Core.Recording
|
namespace BililiveRecorder.Core.Recording
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||||
|
<PackageReference Include="StructLinq" Version="0.26.0" />
|
||||||
<PackageReference Include="System.IO.Pipelines" Version="5.0.1" />
|
<PackageReference Include="System.IO.Pipelines" Version="5.0.1" />
|
||||||
<PackageReference Include="System.Memory" Version="4.5.4" />
|
<PackageReference Include="System.Memory" Version="4.5.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -1,36 +1,27 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using BililiveRecorder.Flv.Pipeline;
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
using StructLinq;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Grouping.Rules
|
namespace BililiveRecorder.Flv.Grouping.Rules
|
||||||
{
|
{
|
||||||
public class DataGroupingRule : IGroupingRule
|
public class DataGroupingRule : IGroupingRule
|
||||||
{
|
{
|
||||||
public bool StartWith(Tag tag) => tag.IsData();
|
private readonly struct DoesNotContainAudioData : IFunction<Tag, bool>
|
||||||
|
|
||||||
public bool AppendWith(Tag tag, LinkedList<Tag> tags, out LinkedList<Tag>? leftover)
|
|
||||||
{
|
{
|
||||||
var shouldAppend =
|
public static DoesNotContainAudioData Instance;
|
||||||
// Tag 是非关键帧数据
|
public bool Eval(Tag element) => element.Type != TagType.Audio || element.Flag == TagFlag.Header;
|
||||||
tag.IsNonKeyframeData()
|
|
||||||
// 或是音频头,并且之前未出现过音频数据
|
|
||||||
|| (tag.Type == TagType.Audio && tag.Flag == TagFlag.Header && tags.All(x => x.Type != TagType.Audio || x.Flag == TagFlag.Header));
|
|
||||||
// || (tag.IsKeyframeData() && tags.All(x => x.IsNonKeyframeData()))
|
|
||||||
|
|
||||||
if (shouldAppend)
|
|
||||||
{
|
|
||||||
tags.AddLast(tag);
|
|
||||||
leftover = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
leftover = new LinkedList<Tag>();
|
|
||||||
leftover.AddLast(tag);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PipelineAction CreatePipelineAction(LinkedList<Tag> tags) => new PipelineDataAction(new List<Tag>(tags));
|
public bool CanStartWith(Tag tag) => tag.IsData();
|
||||||
|
|
||||||
|
public bool CanAppendWith(Tag tag, List<Tag> tags) =>
|
||||||
|
// Tag 是非关键帧数据
|
||||||
|
tag.IsNonKeyframeData()
|
||||||
|
// 或是音频头,并且之前未出现过音频数据
|
||||||
|
|| (tag.Type == TagType.Audio && tag.Flag == TagFlag.Header && tags.ToStructEnumerable().All(ref DoesNotContainAudioData.Instance, x => x));
|
||||||
|
// || (tag.IsKeyframeData() && tags.All(x => x.IsNonKeyframeData()))
|
||||||
|
|
||||||
|
public PipelineAction CreatePipelineAction(List<Tag> tags) => new PipelineDataAction(tags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,14 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using BililiveRecorder.Flv.Pipeline;
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Grouping.Rules
|
namespace BililiveRecorder.Flv.Grouping.Rules
|
||||||
{
|
{
|
||||||
public class EndGroupingRule : IGroupingRule
|
public class EndGroupingRule : IGroupingRule
|
||||||
{
|
{
|
||||||
public bool StartWith(Tag tag) => tag.IsEnd();
|
public bool CanStartWith(Tag tag) => tag.IsEnd();
|
||||||
|
|
||||||
public bool AppendWith(Tag tag, LinkedList<Tag> tags, out LinkedList<Tag>? leftover)
|
public bool CanAppendWith(Tag tag, List<Tag> tags) => false;
|
||||||
{
|
|
||||||
leftover = new LinkedList<Tag>();
|
|
||||||
leftover.AddLast(tag);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PipelineAction CreatePipelineAction(LinkedList<Tag> tags) => new PipelineEndAction(tags.First.Value);
|
public PipelineAction CreatePipelineAction(List<Tag> tags) => new PipelineEndAction(tags[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,14 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using BililiveRecorder.Flv.Pipeline;
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Grouping.Rules
|
namespace BililiveRecorder.Flv.Grouping.Rules
|
||||||
{
|
{
|
||||||
public class HeaderGroupingRule : IGroupingRule
|
public class HeaderGroupingRule : IGroupingRule
|
||||||
{
|
{
|
||||||
public bool StartWith(Tag tag) => tag.IsHeader();
|
public bool CanStartWith(Tag tag) => tag.IsHeader();
|
||||||
|
|
||||||
public bool AppendWith(Tag tag, LinkedList<Tag> tags, out LinkedList<Tag>? leftover)
|
public bool CanAppendWith(Tag tag, List<Tag> tags) => tag.IsHeader();
|
||||||
{
|
|
||||||
if (tag.IsHeader())
|
|
||||||
{
|
|
||||||
tags.AddLast(tag);
|
|
||||||
leftover = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
leftover = new LinkedList<Tag>();
|
|
||||||
leftover.AddLast(tag);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PipelineAction CreatePipelineAction(LinkedList<Tag> tags) => new PipelineHeaderAction(new List<Tag>(tags));
|
public PipelineAction CreatePipelineAction(List<Tag> tags) => new PipelineHeaderAction(tags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,14 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using BililiveRecorder.Flv.Pipeline;
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Grouping.Rules
|
namespace BililiveRecorder.Flv.Grouping.Rules
|
||||||
{
|
{
|
||||||
public class ScriptGroupingRule : IGroupingRule
|
public class ScriptGroupingRule : IGroupingRule
|
||||||
{
|
{
|
||||||
public bool StartWith(Tag tag) => tag.IsScript();
|
public bool CanStartWith(Tag tag) => tag.IsScript();
|
||||||
|
|
||||||
public bool AppendWith(Tag tag, LinkedList<Tag> tags, out LinkedList<Tag>? leftover)
|
public bool CanAppendWith(Tag tag, List<Tag> tags) => false;
|
||||||
{
|
|
||||||
leftover = new LinkedList<Tag>();
|
|
||||||
leftover.AddLast(tag);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PipelineAction CreatePipelineAction(LinkedList<Tag> tags) => new PipelineScriptAction(tags.First.Value);
|
public PipelineAction CreatePipelineAction(List<Tag> tags) => new PipelineScriptAction(tags[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BililiveRecorder.Flv.Grouping.Rules;
|
using BililiveRecorder.Flv.Grouping.Rules;
|
||||||
using BililiveRecorder.Flv.Pipeline;
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Grouping
|
namespace BililiveRecorder.Flv.Grouping
|
||||||
{
|
{
|
||||||
|
@ -14,7 +13,7 @@ namespace BililiveRecorder.Flv.Grouping
|
||||||
private readonly bool leaveOpen;
|
private readonly bool leaveOpen;
|
||||||
private bool disposedValue;
|
private bool disposedValue;
|
||||||
|
|
||||||
private LinkedList<Tag>? leftover;
|
private Tag? leftoverTag;
|
||||||
|
|
||||||
public IFlvTagReader TagReader { get; }
|
public IFlvTagReader TagReader { get; }
|
||||||
public IList<IGroupingRule> GroupingRules { get; }
|
public IList<IGroupingRule> GroupingRules { get; }
|
||||||
|
@ -45,16 +44,12 @@ namespace BililiveRecorder.Flv.Grouping
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
LinkedList<Tag>? queue = null;
|
|
||||||
Tag? firstTag;
|
Tag? firstTag;
|
||||||
|
|
||||||
if (this.leftover is not null && this.leftover.Count > 0)
|
if (this.leftoverTag is not null)
|
||||||
{
|
{
|
||||||
queue = this.leftover;
|
firstTag = this.leftoverTag;
|
||||||
this.leftover = null;
|
this.leftoverTag = null;
|
||||||
|
|
||||||
firstTag = queue.First.Value;
|
|
||||||
queue.RemoveFirst();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -65,38 +60,44 @@ namespace BililiveRecorder.Flv.Grouping
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rule = this.GroupingRules.FirstOrDefault(x => x.StartWith(firstTag));
|
// 查找能处理此 Tag 的分组规则
|
||||||
|
// var rule = this.GroupingRules.FirstOrDefault(x => x.StartWith(firstTag));
|
||||||
|
IGroupingRule? rule = null;
|
||||||
|
{
|
||||||
|
var rules = this.GroupingRules;
|
||||||
|
for (var i = 0; i < rules.Count; i++)
|
||||||
|
{
|
||||||
|
var item = rules[i];
|
||||||
|
if (item.CanStartWith(firstTag))
|
||||||
|
{
|
||||||
|
rule = item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (rule is null)
|
if (rule is null)
|
||||||
throw new Exception("No grouping rule accepting tags: " + firstTag.ToString());
|
throw new Exception("No grouping rule accepting tags: " + firstTag.ToString());
|
||||||
|
|
||||||
var tags = new LinkedList<Tag>();
|
var tags = new List<Tag>(32)
|
||||||
tags.AddLast(firstTag);
|
{
|
||||||
|
firstTag
|
||||||
|
};
|
||||||
|
|
||||||
|
firstTag = null;
|
||||||
|
|
||||||
while (!token.IsCancellationRequested)
|
while (!token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
Tag? tag;
|
var tag = await this.TagReader.ReadTagAsync(token).ConfigureAwait(false);
|
||||||
|
|
||||||
if (queue is not null && queue.Count > 0)
|
if (tag != null && rule.CanAppendWith(tag, tags))
|
||||||
{
|
{
|
||||||
tag = queue.First.Value;
|
tags.Add(tag);
|
||||||
queue.RemoveFirst();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
tag = await this.TagReader.ReadTagAsync(token).ConfigureAwait(false);
|
// 如果数据已经读完,或当前规则不接受此 Tag
|
||||||
}
|
this.leftoverTag = tag;
|
||||||
|
|
||||||
if (tag == null || !rule.AppendWith(tag, tags, out this.leftover))
|
|
||||||
{
|
|
||||||
if (queue is not null && queue.Count > 0)
|
|
||||||
{
|
|
||||||
if (this.leftover is null)
|
|
||||||
this.leftover = queue;
|
|
||||||
else
|
|
||||||
foreach (var item in queue)
|
|
||||||
this.leftover.AddLast(item);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using BililiveRecorder.Flv.Pipeline;
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv
|
namespace BililiveRecorder.Flv
|
||||||
{
|
{
|
||||||
|
@ -10,17 +10,16 @@ namespace BililiveRecorder.Flv
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tags">Current Tags</param>
|
/// <param name="tags">Current Tags</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
bool StartWith(Tag tag);
|
bool CanStartWith(Tag tag);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tag">Tag not yet added to the list</param>
|
/// <param name="tag">Tag not yet added to the list</param>
|
||||||
/// <param name="tags">List of tags</param>
|
/// <param name="tags">List of tags</param>
|
||||||
/// <param name="leftover"></param>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
bool AppendWith(Tag tag, LinkedList<Tag> tags, out LinkedList<Tag>? leftover);
|
bool CanAppendWith(Tag tag, List<Tag> tags);
|
||||||
|
|
||||||
PipelineAction CreatePipelineAction(LinkedList<Tag> tags);
|
PipelineAction CreatePipelineAction(List<Tag> tags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BililiveRecorder.Flv.Pipeline;
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv
|
namespace BililiveRecorder.Flv
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,18 +14,46 @@ namespace BililiveRecorder.Flv
|
||||||
if (!iterator.MoveNext())
|
if (!iterator.MoveNext())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var lastItem = iterator.Current;
|
var previousItem = iterator.Current;
|
||||||
|
|
||||||
while (iterator.MoveNext())
|
while (iterator.MoveNext())
|
||||||
{
|
{
|
||||||
var current = iterator.Current;
|
var currentItem = iterator.Current;
|
||||||
|
|
||||||
if (predicate(lastItem, current))
|
if (predicate(previousItem, currentItem))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
lastItem = current;
|
previousItem = currentItem;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool Any2<TIn, TFunction>(this IEnumerable<TIn> source, ref TFunction function)
|
||||||
|
where TFunction : ITwoInputFunction<TIn, bool>
|
||||||
|
{
|
||||||
|
using var iterator = source.GetEnumerator();
|
||||||
|
|
||||||
|
if (!iterator.MoveNext())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var previousItem = iterator.Current;
|
||||||
|
|
||||||
|
while (iterator.MoveNext())
|
||||||
|
{
|
||||||
|
var currentItem = iterator.Current;
|
||||||
|
|
||||||
|
if (function.Eval(previousItem, currentItem))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
previousItem = currentItem;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface ITwoInputFunction<in TIn, out TOut>
|
||||||
|
{
|
||||||
|
TOut Eval(TIn a, TIn b);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
92
BililiveRecorder.Flv/LinqFunctions.cs
Normal file
92
BililiveRecorder.Flv/LinqFunctions.cs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
using StructLinq;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Flv
|
||||||
|
{
|
||||||
|
public static class LinqFunctions
|
||||||
|
{
|
||||||
|
public static TagIsHeaderStruct TagIsHeader;
|
||||||
|
public readonly struct TagIsHeaderStruct : IFunction<Tag, bool>, IInFunction<Tag, bool>
|
||||||
|
{
|
||||||
|
public bool Eval(Tag element) => element.IsHeader();
|
||||||
|
public bool Eval(in Tag element) => element.IsHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TagIsDataStruct TagIsData;
|
||||||
|
public readonly struct TagIsDataStruct : IFunction<Tag, bool>, IInFunction<Tag, bool>
|
||||||
|
{
|
||||||
|
public bool Eval(Tag element) => element.IsData();
|
||||||
|
public bool Eval(in Tag element) => element.IsData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TagIsVideoStruct TagIsVideo;
|
||||||
|
public readonly struct TagIsVideoStruct : IFunction<Tag, bool>, IInFunction<Tag, bool>
|
||||||
|
{
|
||||||
|
public bool Eval(Tag element) => element.Type == TagType.Video;
|
||||||
|
public bool Eval(in Tag element) => element.Type == TagType.Video;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TagIsAudioStruct TagIsAudio;
|
||||||
|
public readonly struct TagIsAudioStruct : IFunction<Tag, bool>, IInFunction<Tag, bool>
|
||||||
|
{
|
||||||
|
public bool Eval(Tag element) => element.Type == TagType.Audio;
|
||||||
|
public bool Eval(in Tag element) => element.Type == TagType.Audio;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SumSizeOfVideoDataStruct SumSizeOfVideoData;
|
||||||
|
public readonly struct SumSizeOfVideoDataStruct : IFunction<PipelineDataAction, long>, IInFunction<PipelineDataAction, long>
|
||||||
|
{
|
||||||
|
public long Eval(PipelineDataAction element) => element.Tags.ToStructEnumerable().Where(ref TagIsVideo, x => x).Sum(ref SumSizeOfTagByProperty, x => x, x => x);
|
||||||
|
public long Eval(in PipelineDataAction element) => element.Tags.ToStructEnumerable().Where(ref TagIsVideo, x => x).Sum(ref SumSizeOfTagByProperty, x => x, x => x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SumSizeOfVideoDataByNaluStruct SumSizeOfVideoDataByNalu;
|
||||||
|
public readonly struct SumSizeOfVideoDataByNaluStruct : IFunction<PipelineDataAction, long>, IInFunction<PipelineDataAction, long>
|
||||||
|
{
|
||||||
|
public long Eval(PipelineDataAction element) => element.Tags.ToStructEnumerable().Where(ref TagIsVideo, x => x).Sum(ref SumSizeOfTagByPropertyOrNalu, x => x, x => x);
|
||||||
|
public long Eval(in PipelineDataAction element) => element.Tags.ToStructEnumerable().Where(ref TagIsVideo, x => x).Sum(ref SumSizeOfTagByPropertyOrNalu, x => x, x => x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SumSizeOfAudioDataStruct SumSizeOfAudioData;
|
||||||
|
public readonly struct SumSizeOfAudioDataStruct : IFunction<PipelineDataAction, long>, IInFunction<PipelineDataAction, long>
|
||||||
|
{
|
||||||
|
public long Eval(PipelineDataAction element) => element.Tags.ToStructEnumerable().Where(ref TagIsAudio, x => x).Sum(ref SumSizeOfTagByProperty, x => x, x => x);
|
||||||
|
public long Eval(in PipelineDataAction element) => element.Tags.ToStructEnumerable().Where(ref TagIsAudio, x => x).Sum(ref SumSizeOfTagByProperty, x => x, x => x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SumSizeOfTagByPropertyStruct SumSizeOfTagByProperty;
|
||||||
|
public readonly struct SumSizeOfTagByPropertyStruct : IFunction<Tag, long>, IInFunction<Tag, long>
|
||||||
|
{
|
||||||
|
public long Eval(Tag element) => element.Size + (11L + 4L);
|
||||||
|
public long Eval(in Tag element) => element.Size + (11L + 4L);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SumSizeOfTagByPropertyOrNaluStruct SumSizeOfTagByPropertyOrNalu;
|
||||||
|
public readonly struct SumSizeOfTagByPropertyOrNaluStruct : IFunction<Tag, long>, IInFunction<Tag, long>
|
||||||
|
{
|
||||||
|
public long Eval(Tag element) => 11 + 4 + (element.Nalus == null ? element.Size : (5 + element.Nalus.ToStructEnumerable().Sum(ref SumSizeOfNalu, x => x, x => x)));
|
||||||
|
public long Eval(in Tag element) => 11 + 4 + (element.Nalus == null ? element.Size : (5 + element.Nalus.ToStructEnumerable().Sum(ref SumSizeOfNalu, x => x, x => x)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SumSizeOfNaluStruct SumSizeOfNalu;
|
||||||
|
public readonly struct SumSizeOfNaluStruct : IFunction<H264Nalu, long>, IInFunction<H264Nalu, long>
|
||||||
|
{
|
||||||
|
public long Eval(H264Nalu element) => element.FullSize + 4;
|
||||||
|
public long Eval(in H264Nalu element) => element.FullSize + 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CountVideoTagsStruct CountVideoTags;
|
||||||
|
public readonly struct CountVideoTagsStruct : IFunction<PipelineDataAction, int>, IInFunction<PipelineDataAction, int>
|
||||||
|
{
|
||||||
|
public int Eval(PipelineDataAction element) => element.Tags.ToStructEnumerable().Where(ref TagIsVideo, x => x).Count();
|
||||||
|
public int Eval(in PipelineDataAction element) => element.Tags.ToStructEnumerable().Where(ref TagIsVideo, x => x).Count();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CountAudioTagsStruct CountAudioTags;
|
||||||
|
public readonly struct CountAudioTagsStruct : IFunction<PipelineDataAction, int>, IInFunction<PipelineDataAction, int>
|
||||||
|
{
|
||||||
|
public int Eval(PipelineDataAction element) => element.Tags.ToStructEnumerable().Where(ref TagIsAudio, x => x).Count();
|
||||||
|
public int Eval(in PipelineDataAction element) => element.Tags.ToStructEnumerable().Where(ref TagIsAudio, x => x).Count();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
namespace BililiveRecorder.Flv.Pipeline
|
namespace BililiveRecorder.Flv.Pipeline.Actions
|
||||||
{
|
{
|
||||||
public abstract class PipelineAction
|
public abstract class PipelineAction
|
||||||
{
|
{
|
|
@ -1,16 +1,16 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Pipeline
|
namespace BililiveRecorder.Flv.Pipeline.Actions
|
||||||
{
|
{
|
||||||
public class PipelineDataAction : PipelineAction
|
public class PipelineDataAction : PipelineAction
|
||||||
{
|
{
|
||||||
public PipelineDataAction(List<Tag> tags)
|
public PipelineDataAction(IReadOnlyList<Tag> tags)
|
||||||
{
|
{
|
||||||
this.Tags = tags ?? throw new ArgumentNullException(nameof(tags));
|
this.Tags = tags ?? throw new ArgumentNullException(nameof(tags));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Tag> Tags { get; set; }
|
public IReadOnlyList<Tag> Tags { get; set; }
|
||||||
|
|
||||||
public override PipelineAction Clone() => new PipelineDataAction(new List<Tag>(this.Tags));
|
public override PipelineAction Clone() => new PipelineDataAction(new List<Tag>(this.Tags));
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
namespace BililiveRecorder.Flv.Pipeline
|
namespace BililiveRecorder.Flv.Pipeline.Actions
|
||||||
{
|
{
|
||||||
public class PipelineDisconnectAction : PipelineAction
|
public class PipelineDisconnectAction : PipelineAction
|
||||||
{
|
{
|
|
@ -1,4 +1,4 @@
|
||||||
namespace BililiveRecorder.Flv.Pipeline
|
namespace BililiveRecorder.Flv.Pipeline.Actions
|
||||||
{
|
{
|
||||||
public class PipelineEndAction : PipelineAction
|
public class PipelineEndAction : PipelineAction
|
||||||
{
|
{
|
|
@ -2,7 +2,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Pipeline
|
namespace BililiveRecorder.Flv.Pipeline.Actions
|
||||||
{
|
{
|
||||||
public class PipelineHeaderAction : PipelineAction
|
public class PipelineHeaderAction : PipelineAction
|
||||||
{
|
{
|
|
@ -2,7 +2,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Pipeline
|
namespace BililiveRecorder.Flv.Pipeline.Actions
|
||||||
{
|
{
|
||||||
public class PipelineLogAlternativeHeaderAction : PipelineAction
|
public class PipelineLogAlternativeHeaderAction : PipelineAction
|
||||||
{
|
{
|
|
@ -1,4 +1,4 @@
|
||||||
namespace BililiveRecorder.Flv.Pipeline
|
namespace BililiveRecorder.Flv.Pipeline.Actions
|
||||||
{
|
{
|
||||||
public class PipelineNewFileAction : PipelineAction
|
public class PipelineNewFileAction : PipelineAction
|
||||||
{
|
{
|
|
@ -1,6 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Pipeline
|
namespace BililiveRecorder.Flv.Pipeline.Actions
|
||||||
{
|
{
|
||||||
public class PipelineScriptAction : PipelineAction
|
public class PipelineScriptAction : PipelineAction
|
||||||
{
|
{
|
|
@ -2,6 +2,8 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
using StructLinq;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Pipeline
|
namespace BililiveRecorder.Flv.Pipeline
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,9 +26,6 @@ namespace BililiveRecorder.Flv.Pipeline
|
||||||
builder
|
builder
|
||||||
.Add<HandleEndTagRule>()
|
.Add<HandleEndTagRule>()
|
||||||
.Add<HandleDelayedAudioHeaderRule>()
|
.Add<HandleDelayedAudioHeaderRule>()
|
||||||
// TODO .Add<CheckMissingKeyframeRule>()
|
|
||||||
// .Add<UpdateDataTagOrderRule>()
|
|
||||||
// .Add<CheckDiscontinuityRule>()
|
|
||||||
.Add<UpdateTimestampOffsetRule>()
|
.Add<UpdateTimestampOffsetRule>()
|
||||||
.Add<UpdateTimestampJumpRule>()
|
.Add<UpdateTimestampJumpRule>()
|
||||||
.Add<HandleNewScriptRule>()
|
.Add<HandleNewScriptRule>()
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Pipeline.Rules
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 检查分块内时间戳问题
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// 到目前为止还未发现有在一个 GOP 内出现时间戳异常问题<br/>
|
|
||||||
/// 本规则是为了预防实际使用中遇到意外情况<br/>
|
|
||||||
/// <br/>
|
|
||||||
/// 本规则应该放在所有规则前面
|
|
||||||
/// </remarks>
|
|
||||||
public class CheckDiscontinuityRule : ISimpleProcessingRule
|
|
||||||
{
|
|
||||||
private const int MAX_ALLOWED_DIFF = 1000 * 10; // 10 seconds
|
|
||||||
|
|
||||||
private static readonly ProcessingComment Comment1 = new ProcessingComment(CommentType.Unrepairable, "Flv Chunk 内出现时间戳跳变(变小)");
|
|
||||||
private static readonly ProcessingComment Comment2 = new ProcessingComment(CommentType.Unrepairable, "Flv Chunk 内出现时间戳跳变(间隔过大)");
|
|
||||||
|
|
||||||
public void Run(FlvProcessingContext context, Action next)
|
|
||||||
{
|
|
||||||
context.PerActionRun(this.RunPerAction);
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<PipelineAction?> RunPerAction(FlvProcessingContext context, PipelineAction action)
|
|
||||||
{
|
|
||||||
if (action is PipelineDataAction data)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < data.Tags.Count - 1; i++)
|
|
||||||
{
|
|
||||||
var f1 = data.Tags[i];
|
|
||||||
var f2 = data.Tags[i + 1];
|
|
||||||
|
|
||||||
if (f1.Timestamp > f2.Timestamp)
|
|
||||||
{
|
|
||||||
context.AddComment(Comment1);
|
|
||||||
yield return PipelineDisconnectAction.Instance;
|
|
||||||
yield return null;
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
else if ((f2.Timestamp - f1.Timestamp) > MAX_ALLOWED_DIFF)
|
|
||||||
{
|
|
||||||
context.AddComment(Comment2);
|
|
||||||
yield return PipelineDisconnectAction.Instance;
|
|
||||||
yield return null;
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return data;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
yield return action;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Pipeline.Rules
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 检查缺少关键帧的问题
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// 到目前为止还未发现有出现过此问题<br/>
|
|
||||||
/// 本规则是为了预防实际使用中遇到意外情况<br/>
|
|
||||||
/// <br/>
|
|
||||||
/// 本规则应该放在所有规则前面
|
|
||||||
/// </remarks>
|
|
||||||
public class CheckMissingKeyframeRule : ISimpleProcessingRule
|
|
||||||
{
|
|
||||||
private static readonly ProcessingComment comment = new ProcessingComment(CommentType.Unrepairable, "Flv Chunk 内缺少关键帧");
|
|
||||||
|
|
||||||
public void Run(FlvProcessingContext context, Action next)
|
|
||||||
{
|
|
||||||
// context.PerActionRun(this.RunPerAction);
|
|
||||||
// 暂时禁用此规则,必要性待定
|
|
||||||
// TODO
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<PipelineAction?> RunPerAction(FlvProcessingContext context, PipelineAction action)
|
|
||||||
{
|
|
||||||
if (action is PipelineDataAction data)
|
|
||||||
{
|
|
||||||
var f = data.Tags.FirstOrDefault(x => x.Type == TagType.Video);
|
|
||||||
if (f == null || (0 == (f.Flag & TagFlag.Keyframe)))
|
|
||||||
{
|
|
||||||
context.AddComment(comment);
|
|
||||||
yield return PipelineDisconnectAction.Instance;
|
|
||||||
yield return null;
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
yield return action;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
yield return action;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
using StructLinq;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Pipeline.Rules
|
namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
{
|
{
|
||||||
|
@ -12,7 +14,8 @@ namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class HandleDelayedAudioHeaderRule : ISimpleProcessingRule
|
public class HandleDelayedAudioHeaderRule : ISimpleProcessingRule
|
||||||
{
|
{
|
||||||
private static readonly ProcessingComment comment = new ProcessingComment(CommentType.DecodingHeader, "检测到延后收到的音频头");
|
private static readonly ProcessingComment comment1 = new ProcessingComment(CommentType.Unrepairable, "音频数据出现在音频头之前");
|
||||||
|
private static readonly ProcessingComment comment2 = new ProcessingComment(CommentType.DecodingHeader, "检测到延后收到的音频头");
|
||||||
|
|
||||||
public void Run(FlvProcessingContext context, Action next)
|
public void Run(FlvProcessingContext context, Action next)
|
||||||
{
|
{
|
||||||
|
@ -25,25 +28,42 @@ namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
if (action is PipelineDataAction data)
|
if (action is PipelineDataAction data)
|
||||||
{
|
{
|
||||||
var tags = data.Tags;
|
var tags = data.Tags;
|
||||||
if (tags.Any(x => x.IsHeader()))
|
// 如果分组内含有 Heaer
|
||||||
|
if (tags.ToStructEnumerable().Any(ref LinqFunctions.TagIsHeader, x => x))
|
||||||
{
|
{
|
||||||
context.AddComment(comment);
|
|
||||||
|
|
||||||
var index = tags.IndexOf(tags.Last(x => x.Flag == TagFlag.Header));
|
|
||||||
for (var i = 0; i < index; i++)
|
|
||||||
{
|
{
|
||||||
if (tags[i].Type == TagType.Audio)
|
var shouldReportError = false;
|
||||||
|
for (var i = tags.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
// 在一段数据内 Header 之前出现了音频数据
|
if (tags[i].Type == TagType.Audio)
|
||||||
yield return PipelineDisconnectAction.Instance;
|
{
|
||||||
yield return null;
|
if (tags[i].Flag != TagFlag.None)
|
||||||
yield break;
|
{
|
||||||
|
// 发现了 Audio Header
|
||||||
|
shouldReportError = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 在一段数据内 Header 之前出现了音频数据
|
||||||
|
if (shouldReportError)
|
||||||
|
{
|
||||||
|
context.AddComment(comment1);
|
||||||
|
yield return PipelineDisconnectAction.Instance;
|
||||||
|
yield return PipelineNewFileAction.Instance;
|
||||||
|
yield return null;
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var headerTags = tags.Where(x => x.Flag == TagFlag.Header).ToList();
|
context.AddComment(comment2);
|
||||||
|
|
||||||
|
var headerTags = tags.ToStructEnumerable().Where(ref LinqFunctions.TagIsHeader, x => x).ToArray();
|
||||||
var newHeaderAction = new PipelineHeaderAction(headerTags);
|
var newHeaderAction = new PipelineHeaderAction(headerTags);
|
||||||
var dataTags = tags.Where(x => x.Flag != TagFlag.Header).ToList();
|
|
||||||
|
var dataTags = tags.ToStructEnumerable().Except(headerTags.ToStructEnumerable(), x => x, x => x).ToArray();
|
||||||
var newDataAction = new PipelineDataAction(dataTags);
|
var newDataAction = new PipelineDataAction(dataTags);
|
||||||
|
|
||||||
yield return newHeaderAction;
|
yield return newHeaderAction;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Pipeline.Rules
|
namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
using StructLinq;
|
||||||
|
using StructLinq.Where;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Pipeline.Rules
|
namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
{
|
{
|
||||||
|
@ -40,8 +43,8 @@ namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
// 音频 视频 分别单独处理
|
// 音频 视频 分别单独处理
|
||||||
var group = header.AllTags.GroupBy(x => x.Type);
|
var group = header.AllTags.GroupBy(x => x.Type);
|
||||||
|
|
||||||
var currentVideoHeader = SelectHeader(ref multiple_header_present, group.FirstOrDefault(x => x.Key == TagType.Video));
|
var currentVideoHeader = SelectHeader(ref multiple_header_present, header.AllTags.ToStructEnumerable().Where(ref LinqFunctions.TagIsVideo, x => x));
|
||||||
var currentAudioHeader = SelectHeader(ref multiple_header_present, group.FirstOrDefault(x => x.Key == TagType.Audio));
|
var currentAudioHeader = SelectHeader(ref multiple_header_present, header.AllTags.ToStructEnumerable().Where(ref LinqFunctions.TagIsAudio, x => x));
|
||||||
|
|
||||||
if (multiple_header_present)
|
if (multiple_header_present)
|
||||||
context.AddComment(MultipleHeaderComment);
|
context.AddComment(MultipleHeaderComment);
|
||||||
|
@ -91,30 +94,29 @@ namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
yield return action;
|
yield return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Tag? SelectHeader(ref bool multiple_header_present, IGrouping<TagType, Tag> tagGroup)
|
private static Tag? SelectHeader<TEnumerable, TEnumerator, TFunction>(ref bool multiple_header_present, WhereEnumerable<Tag, TEnumerable, TEnumerator, TFunction> tags)
|
||||||
|
where TEnumerable : struct, IStructEnumerable<Tag, TEnumerator>
|
||||||
|
where TEnumerator : struct, IStructEnumerator<Tag>
|
||||||
|
where TFunction : struct, IFunction<Tag, bool>
|
||||||
{
|
{
|
||||||
Tag? currentHeader;
|
Tag? currentHeader;
|
||||||
if (tagGroup != null)
|
|
||||||
|
if (tags.Count(x => x) > 1)
|
||||||
{
|
{
|
||||||
// 检查是否存在 **多个** **不同的** Header
|
// 检查是否存在 **多个** **不同的** Header
|
||||||
if (tagGroup.Count() > 1)
|
var first = tags.First(x => x);
|
||||||
{
|
|
||||||
var first = tagGroup.First();
|
|
||||||
|
|
||||||
if (tagGroup.Skip(1).All(x => first.BinaryData?.SequenceEqual(x.BinaryData) ?? false))
|
if (tags.Skip(1, x => x).All(x => first.BinaryData?.SequenceEqual(x.BinaryData) ?? false))
|
||||||
currentHeader = first;
|
currentHeader = first;
|
||||||
else
|
|
||||||
{
|
|
||||||
// 默认最后一个为正确的
|
|
||||||
currentHeader = tagGroup.Last();
|
|
||||||
multiple_header_present = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
currentHeader = tagGroup.FirstOrDefault();
|
{
|
||||||
|
// 默认最后一个为正确的
|
||||||
|
currentHeader = tags.Last(x => x);
|
||||||
|
multiple_header_present = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
currentHeader = null;
|
currentHeader = tags.FirstOrDefault(x => x);
|
||||||
|
|
||||||
return currentHeader;
|
return currentHeader;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using BililiveRecorder.Flv.Amf;
|
using BililiveRecorder.Flv.Amf;
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Pipeline.Rules
|
namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,8 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using BililiveRecorder.Flv;
|
using BililiveRecorder.Flv;
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
using StructLinq;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Pipeline.Rules
|
namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
{
|
{
|
||||||
|
@ -80,7 +82,7 @@ namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对比历史特征
|
// 对比历史特征
|
||||||
if (history.Any(x => x.SequenceEqual(feature)))
|
if (history.ToStructEnumerable().Any(x => x.SequenceEqual(feature), x => x))
|
||||||
{
|
{
|
||||||
context.AddComment(comment);
|
context.AddComment(comment);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Pipeline.Rules
|
namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Pipeline.Rules
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 修复 Tag 错位等时间戳相关问题
|
|
||||||
/// </summary>
|
|
||||||
public class UpdateDataTagOrderRule : ISimpleProcessingRule
|
|
||||||
{
|
|
||||||
public void Run(FlvProcessingContext context, Action next)
|
|
||||||
{
|
|
||||||
context.PerActionRun(this.RunPerAction);
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<PipelineAction?> RunPerAction(FlvProcessingContext context, PipelineAction action)
|
|
||||||
{
|
|
||||||
if (action is not PipelineDataAction data)
|
|
||||||
{
|
|
||||||
yield return action;
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果一切正常,直接跳过
|
|
||||||
if (data.Tags.Any2((t1, t2) => t1.Timestamp > t2.Timestamp))
|
|
||||||
{
|
|
||||||
// 排序
|
|
||||||
data.Tags = data.Tags.OrderBy(x => x.Timestamp).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
using StructLinq;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Pipeline.Rules
|
namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
{
|
{
|
||||||
|
@ -28,8 +31,27 @@ namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
foreach (var action in context.Actions)
|
foreach (var action in context.Actions)
|
||||||
{
|
{
|
||||||
if (action is PipelineDataAction dataAction)
|
if (action is PipelineDataAction dataAction)
|
||||||
{
|
{ // SetDataTimestamp
|
||||||
this.SetDataTimestamp(dataAction.Tags, ts, context);
|
var tags = dataAction.Tags;
|
||||||
|
var currentTimestamp = tags[0].Timestamp;
|
||||||
|
var diff = currentTimestamp - ts.LastOriginal;
|
||||||
|
if (diff < 0)
|
||||||
|
{
|
||||||
|
context.AddComment(new ProcessingComment(CommentType.TimestampJump, $"时间戳变小, curr: {currentTimestamp}, diff: {diff}"));
|
||||||
|
ts.CurrentOffset = currentTimestamp - ts.NextTimestampTarget;
|
||||||
|
}
|
||||||
|
else if (diff > JUMP_THRESHOLD)
|
||||||
|
{
|
||||||
|
context.AddComment(new ProcessingComment(CommentType.TimestampJump, $"时间戳间隔过大, curr: {currentTimestamp}, diff: {diff}"));
|
||||||
|
ts.CurrentOffset = currentTimestamp - ts.NextTimestampTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
ts.LastOriginal = tags[tags.Count - 1].Timestamp;
|
||||||
|
|
||||||
|
foreach (var tag in tags)
|
||||||
|
tag.Timestamp -= ts.CurrentOffset;
|
||||||
|
|
||||||
|
ts.NextTimestampTarget = this.CalculateNewTarget(tags);
|
||||||
}
|
}
|
||||||
else if (action is PipelineEndAction endAction)
|
else if (action is PipelineEndAction endAction)
|
||||||
{
|
{
|
||||||
|
@ -64,45 +86,24 @@ namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetDataTimestamp(IReadOnlyList<Tag> tags, TimestampStore ts, FlvProcessingContext context)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
{
|
|
||||||
var currentTimestamp = tags[0].Timestamp;
|
|
||||||
var diff = currentTimestamp - ts.LastOriginal;
|
|
||||||
if (diff < 0)
|
|
||||||
{
|
|
||||||
context.AddComment(new ProcessingComment(CommentType.TimestampJump, $"时间戳变小, curr: {currentTimestamp}, diff: {diff}"));
|
|
||||||
ts.CurrentOffset = currentTimestamp - ts.NextTimestampTarget;
|
|
||||||
}
|
|
||||||
else if (diff > JUMP_THRESHOLD)
|
|
||||||
{
|
|
||||||
context.AddComment(new ProcessingComment(CommentType.TimestampJump, $"时间戳间隔过大, curr: {currentTimestamp}, diff: {diff}"));
|
|
||||||
ts.CurrentOffset = currentTimestamp - ts.NextTimestampTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
ts.LastOriginal = tags.Last().Timestamp;
|
|
||||||
|
|
||||||
foreach (var tag in tags)
|
|
||||||
tag.Timestamp -= ts.CurrentOffset;
|
|
||||||
|
|
||||||
ts.NextTimestampTarget = this.CalculateNewTarget(tags);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int CalculateNewTarget(IReadOnlyList<Tag> tags)
|
private int CalculateNewTarget(IReadOnlyList<Tag> tags)
|
||||||
{
|
{
|
||||||
// 有可能出现只有音频或只有视频的情况
|
// 有可能出现只有音频或只有视频的情况
|
||||||
int video = 0, audio = 0;
|
int video = 0, audio = 0;
|
||||||
|
|
||||||
if (tags.Any(x => x.Type == TagType.Video))
|
if (tags.ToStructEnumerable().Any(ref LinqFunctions.TagIsVideo, x => x))
|
||||||
video = CalculatePerChannel(tags, VIDEO_DURATION_FALLBACK, VIDEO_DURATION_MAX, VIDEO_DURATION_MIN, TagType.Video);
|
video = CalculatePerChannel(tags, VIDEO_DURATION_FALLBACK, VIDEO_DURATION_MAX, VIDEO_DURATION_MIN, TagType.Video);
|
||||||
|
|
||||||
if (tags.Any(x => x.Type == TagType.Audio))
|
if (tags.ToStructEnumerable().Any(ref LinqFunctions.TagIsAudio, x => x))
|
||||||
audio = CalculatePerChannel(tags, AUDIO_DURATION_FALLBACK, AUDIO_DURATION_MAX, AUDIO_DURATION_MIN, TagType.Audio);
|
audio = CalculatePerChannel(tags, AUDIO_DURATION_FALLBACK, AUDIO_DURATION_MAX, AUDIO_DURATION_MIN, TagType.Audio);
|
||||||
|
|
||||||
return Math.Max(video, audio);
|
return Math.Max(video, audio);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
static int CalculatePerChannel(IReadOnlyList<Tag> tags, int fallback, int max, int min, TagType type)
|
static int CalculatePerChannel(IReadOnlyList<Tag> tags, int fallback, int max, int min, TagType type)
|
||||||
{
|
{
|
||||||
var sample = tags.Where(x => x.Type == type).Take(2).ToArray();
|
var sample = tags.ToStructEnumerable().Where(x => x.Type == type).Take(2).ToArray();
|
||||||
int durationPerTag;
|
int durationPerTag;
|
||||||
if (sample.Length != 2)
|
if (sample.Length != 2)
|
||||||
{
|
{
|
||||||
|
@ -116,7 +117,7 @@ namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
durationPerTag = fallback;
|
durationPerTag = fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
return durationPerTag + tags.Last(x => x.Type == type).Timestamp;
|
return durationPerTag + tags.ToStructEnumerable().Last(x => x.Type == type).Timestamp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
using StructLinq;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Pipeline.Rules
|
namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
{
|
{
|
||||||
|
@ -15,20 +18,25 @@ namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CheckIfNormal(IEnumerable<Tag> data) => !data.Any2((a, b) => a.Timestamp > b.Timestamp);
|
private readonly struct IsNotNormal : ITwoInputFunction<Tag, bool>
|
||||||
|
{
|
||||||
|
public static IsNotNormal Instance;
|
||||||
|
public bool Eval(Tag a, Tag b) => a.Timestamp > b.Timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
private IEnumerable<PipelineAction?> RunPerAction(FlvProcessingContext context, PipelineAction action)
|
private IEnumerable<PipelineAction?> RunPerAction(FlvProcessingContext context, PipelineAction action)
|
||||||
{
|
{
|
||||||
if (action is PipelineDataAction data)
|
if (action is PipelineDataAction data)
|
||||||
{
|
{
|
||||||
var isNormal = this.CheckIfNormal(data.Tags);
|
var isNotNormal = data.Tags.Any2(ref IsNotNormal.Instance);
|
||||||
if (isNormal)
|
if (!isNotNormal)
|
||||||
{
|
{
|
||||||
|
// 没有问题
|
||||||
yield return data;
|
yield return data;
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(this.CheckIfNormal(data.Tags.Where(x => x.Type == TagType.Audio)) && this.CheckIfNormal(data.Tags.Where(x => x.Type == TagType.Video))))
|
if (data.Tags.Where(x => x.Type == TagType.Audio).Any2(ref IsNotNormal.Instance) || data.Tags.Where(x => x.Type == TagType.Video).Any2(ref IsNotNormal.Instance))
|
||||||
{
|
{
|
||||||
// 音频或视频自身就有问题,没救了
|
// 音频或视频自身就有问题,没救了
|
||||||
yield return PipelineDisconnectAction.Instance;
|
yield return PipelineDisconnectAction.Instance;
|
||||||
|
@ -37,146 +45,118 @@ namespace BililiveRecorder.Flv.Pipeline.Rules
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var oc = new OffsetCalculator();
|
/*
|
||||||
|
* 设定做调整的为视频帧,参照每个视频帧左右(左为前、右为后)的音频帧的时间戳
|
||||||
|
* 计算出最多和最少能符合“不小于前面的帧并且不大于后面的帧”的要求的偏移量
|
||||||
|
* 如果当前偏移量比总偏移量要求更严,则使用当前偏移量范围作为总偏移量范围
|
||||||
|
* */
|
||||||
|
var offset = 0;
|
||||||
|
|
||||||
foreach (var tag in data.Tags)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
oc.AddTag(tag);
|
static void ReduceOffsetRange(ref int maxOffset, ref int minOffset, Tag? leftAudio, Tag? rightAudio, Stack<Tag> tags)
|
||||||
|
|
||||||
if (oc.Calculate(out var videoOffset))
|
|
||||||
{
|
{
|
||||||
if (videoOffset != 0)
|
while (tags.Count > 0)
|
||||||
{
|
{
|
||||||
context.AddComment(new ProcessingComment(CommentType.TimestampOffset, $"音视频时间戳偏移, D: {videoOffset}"));
|
var video = tags.Pop();
|
||||||
|
|
||||||
foreach (var tag in data.Tags)
|
if (leftAudio is not null)
|
||||||
if (tag.Type == TagType.Video)
|
{
|
||||||
tag.Timestamp += videoOffset;
|
var min = leftAudio.Timestamp - video.Timestamp;
|
||||||
|
if (minOffset < min)
|
||||||
|
minOffset = min;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rightAudio is not null)
|
||||||
|
{
|
||||||
|
var max = rightAudio.Timestamp - video.Timestamp;
|
||||||
|
if (maxOffset > max)
|
||||||
|
maxOffset = max;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
yield return data;
|
Tag? lastAudio = null;
|
||||||
yield break;
|
var tags = new Stack<Tag>();
|
||||||
|
var maxOffset = int.MaxValue;
|
||||||
|
var minOffset = int.MinValue;
|
||||||
|
|
||||||
|
for (var i = 0; i < data.Tags.Count; i++)
|
||||||
|
{
|
||||||
|
var tag = data.Tags[i];
|
||||||
|
if (tag.Type == TagType.Audio)
|
||||||
|
{
|
||||||
|
ReduceOffsetRange(ref maxOffset, ref minOffset, lastAudio, tag, tags);
|
||||||
|
lastAudio = tag;
|
||||||
|
}
|
||||||
|
else if (tag.Type == TagType.Video)
|
||||||
|
{
|
||||||
|
tags.Push(tag);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new ArgumentException("unexpected tag type");
|
||||||
|
}
|
||||||
|
|
||||||
|
ReduceOffsetRange(ref maxOffset, ref minOffset, lastAudio, null, tags);
|
||||||
|
|
||||||
|
if (minOffset == maxOffset)
|
||||||
|
{ // 理想情况允许偏移范围只有一个值
|
||||||
|
offset = minOffset;
|
||||||
|
goto validOffset;
|
||||||
|
}
|
||||||
|
else if (minOffset < maxOffset)
|
||||||
|
{ // 允许偏移的值是一个范围
|
||||||
|
if (minOffset != int.MinValue)
|
||||||
|
{
|
||||||
|
if (maxOffset != int.MaxValue)
|
||||||
|
{ // 有一个有效范围,取平均值
|
||||||
|
offset = (int)(((long)minOffset + maxOffset) / 2L);
|
||||||
|
goto validOffset;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // 无效最大偏移,以最小偏移为准
|
||||||
|
offset = minOffset + 1;
|
||||||
|
goto validOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (maxOffset != int.MaxValue)
|
||||||
|
{ // 无效最小偏移,以最大偏移为准
|
||||||
|
offset = maxOffset - 1;
|
||||||
|
goto validOffset;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // 无效结果
|
||||||
|
goto invalidOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{ // 范围无效
|
||||||
context.AddComment(comment2);
|
goto invalidOffset;
|
||||||
yield return PipelineDisconnectAction.Instance;
|
|
||||||
yield break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validOffset:
|
||||||
|
if (offset != 0)
|
||||||
|
{
|
||||||
|
context.AddComment(new ProcessingComment(CommentType.TimestampOffset, $"音视频时间戳偏移, D: {offset}"));
|
||||||
|
|
||||||
|
foreach (var tag in data.Tags)
|
||||||
|
if (tag.Type == TagType.Video)
|
||||||
|
tag.Timestamp += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return data;
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
invalidOffset:
|
||||||
|
context.AddComment(comment2);
|
||||||
|
yield return PipelineDisconnectAction.Instance;
|
||||||
|
yield break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
yield return action;
|
yield return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 音视频偏差量计算
|
|
||||||
/// </summary>
|
|
||||||
private class OffsetCalculator
|
|
||||||
{
|
|
||||||
/* 算法思路和原理
|
|
||||||
* 设定作调整的为视频帧,参照每个视频帧左右(左为前、右为后)的音频帧的时间戳
|
|
||||||
* 计算出最多和最少能符合“不小于前面的帧并且不大于后面的帧”的要求的偏移量
|
|
||||||
* 如果当前偏移量比总偏移量要求更严,则使用当前偏移量范围作为总偏移量范围
|
|
||||||
* */
|
|
||||||
|
|
||||||
private Tag? lastAudio = null;
|
|
||||||
private readonly Stack<Tag> tags = new Stack<Tag>();
|
|
||||||
|
|
||||||
private int maxOffset = int.MaxValue;
|
|
||||||
private int minOffset = int.MinValue;
|
|
||||||
|
|
||||||
public void AddTag(Tag tag)
|
|
||||||
{
|
|
||||||
if (tag.Type == TagType.Audio)
|
|
||||||
{
|
|
||||||
this.ReduceOffsetRange(this.lastAudio, tag);
|
|
||||||
this.lastAudio = tag;
|
|
||||||
}
|
|
||||||
else if (tag.Type == TagType.Video)
|
|
||||||
{
|
|
||||||
this.tags.Push(tag);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw new ArgumentException("unexpected tag type");
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Calculate(out int offset)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
var last = this.lastAudio;
|
|
||||||
this.lastAudio = null;
|
|
||||||
this.ReduceOffsetRange(last, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.minOffset == this.maxOffset)
|
|
||||||
{
|
|
||||||
// 理想情况允许偏移范围只有一个值
|
|
||||||
offset = this.minOffset;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (this.minOffset < this.maxOffset)
|
|
||||||
{
|
|
||||||
// 允许偏移的值是一个范围
|
|
||||||
if (this.minOffset != int.MinValue)
|
|
||||||
{
|
|
||||||
if (this.maxOffset != int.MaxValue)
|
|
||||||
{
|
|
||||||
// 有一个有效范围,取平均值
|
|
||||||
offset = (int)(((long)this.minOffset + this.maxOffset) / 2L);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 无效最大偏移,以最小偏移为准
|
|
||||||
offset = this.minOffset + 1;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (this.maxOffset != int.MaxValue)
|
|
||||||
{
|
|
||||||
// 无效最小偏移,以最大偏移为准
|
|
||||||
offset = this.maxOffset - 1;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 无效结果
|
|
||||||
offset = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 范围无效
|
|
||||||
offset = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReduceOffsetRange(Tag? leftAudio, Tag? rightAudio)
|
|
||||||
{
|
|
||||||
while (this.tags.Count > 0)
|
|
||||||
{
|
|
||||||
var video = this.tags.Pop();
|
|
||||||
|
|
||||||
if (leftAudio is not null)
|
|
||||||
{
|
|
||||||
var min = leftAudio.Timestamp - video.Timestamp;
|
|
||||||
if (this.minOffset < min)
|
|
||||||
this.minOffset = min;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rightAudio is not null)
|
|
||||||
{
|
|
||||||
var max = rightAudio.Timestamp - video.Timestamp;
|
|
||||||
if (this.maxOffset > max)
|
|
||||||
this.maxOffset = max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,8 @@ namespace BililiveRecorder.Flv
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool IsHeader(this Tag tag)
|
public static bool IsHeader(this Tag tag)
|
||||||
=> (tag.Type == TagType.Video || tag.Type == TagType.Audio)
|
=> (0 != (tag.Flag & TagFlag.Header))
|
||||||
&& (0 != (tag.Flag & TagFlag.Header));
|
&& (tag.Type == TagType.Video || tag.Type == TagType.Audio);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool IsEnd(this Tag tag)
|
public static bool IsEnd(this Tag tag)
|
||||||
|
@ -24,7 +24,8 @@ namespace BililiveRecorder.Flv
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool IsData(this Tag tag)
|
public static bool IsData(this Tag tag)
|
||||||
=> tag.Type != TagType.Script && (0 == (tag.Flag & (TagFlag.Header | TagFlag.End)));
|
=> (0 == (tag.Flag & (TagFlag.Header | TagFlag.End)))
|
||||||
|
&& (tag.Type == TagType.Video || tag.Type == TagType.Audio);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool IsNonKeyframeData(this Tag tag)
|
public static bool IsNonKeyframeData(this Tag tag)
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BililiveRecorder.Flv.Amf;
|
using BililiveRecorder.Flv.Amf;
|
||||||
using BililiveRecorder.Flv.Pipeline;
|
using BililiveRecorder.Flv.Pipeline;
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
|
||||||
namespace BililiveRecorder.Flv.Writer
|
namespace BililiveRecorder.Flv.Writer
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,6 +10,7 @@ using BililiveRecorder.Flv.Amf;
|
||||||
using BililiveRecorder.Flv.Grouping;
|
using BililiveRecorder.Flv.Grouping;
|
||||||
using BililiveRecorder.Flv.Parser;
|
using BililiveRecorder.Flv.Parser;
|
||||||
using BililiveRecorder.Flv.Pipeline;
|
using BililiveRecorder.Flv.Pipeline;
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
using BililiveRecorder.Flv.Writer;
|
using BililiveRecorder.Flv.Writer;
|
||||||
using BililiveRecorder.Flv.Xml;
|
using BililiveRecorder.Flv.Xml;
|
||||||
using BililiveRecorder.ToolBox.ProcessingRules;
|
using BililiveRecorder.ToolBox.ProcessingRules;
|
||||||
|
|
|
@ -9,6 +9,7 @@ using BililiveRecorder.Flv;
|
||||||
using BililiveRecorder.Flv.Grouping;
|
using BililiveRecorder.Flv.Grouping;
|
||||||
using BililiveRecorder.Flv.Parser;
|
using BililiveRecorder.Flv.Parser;
|
||||||
using BililiveRecorder.Flv.Pipeline;
|
using BililiveRecorder.Flv.Pipeline;
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
using BililiveRecorder.Flv.Writer;
|
using BililiveRecorder.Flv.Writer;
|
||||||
using BililiveRecorder.Flv.Xml;
|
using BililiveRecorder.Flv.Xml;
|
||||||
using BililiveRecorder.ToolBox.ProcessingRules;
|
using BililiveRecorder.ToolBox.ProcessingRules;
|
||||||
|
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using BililiveRecorder.Flv;
|
using BililiveRecorder.Flv;
|
||||||
using BililiveRecorder.Flv.Pipeline;
|
using BililiveRecorder.Flv.Pipeline;
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
|
using StructLinq;
|
||||||
|
|
||||||
namespace BililiveRecorder.ToolBox.ProcessingRules
|
namespace BililiveRecorder.ToolBox.ProcessingRules
|
||||||
{
|
{
|
||||||
|
@ -40,13 +42,14 @@ namespace BililiveRecorder.ToolBox.ProcessingRules
|
||||||
var stat = new FlvStats
|
var stat = new FlvStats
|
||||||
{
|
{
|
||||||
FrameCount = timestamps.Count,
|
FrameCount = timestamps.Count,
|
||||||
FrameDurations = timestamps.Select((time, i) => i == 0 ? 0 : time - timestamps[i - 1])
|
FrameDurations = timestamps
|
||||||
.Skip(1)
|
.Select((time, i) => i == 0 ? 0 : time - timestamps[i - 1])
|
||||||
.GroupBy(x => x)
|
.Skip(1)
|
||||||
.ToDictionary(x => x.Key, x => x.Count())
|
.GroupBy(x => x)
|
||||||
.OrderByDescending(x => x.Value)
|
.ToDictionary(x => x.Key, x => x.Count())
|
||||||
.ThenByDescending(x => x.Key)
|
.OrderByDescending(x => x.Value)
|
||||||
.ToDictionary(x => x.Key, x => x.Value)
|
.ThenByDescending(x => x.Key)
|
||||||
|
.ToDictionary(x => x.Key, x => x.Value)
|
||||||
};
|
};
|
||||||
stat.FramePerSecond = 1000d / stat.FrameDurations.Select(x => x.Key * ((double)x.Value / timestamps.Count)).Sum();
|
stat.FramePerSecond = 1000d / stat.FrameDurations.Select(x => x.Key * ((double)x.Value / timestamps.Count)).Sum();
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
||||||
using BililiveRecorder.Flv.Grouping;
|
using BililiveRecorder.Flv.Grouping;
|
||||||
using BililiveRecorder.Flv.Parser;
|
using BililiveRecorder.Flv.Parser;
|
||||||
using BililiveRecorder.Flv.Pipeline;
|
using BililiveRecorder.Flv.Pipeline;
|
||||||
|
using BililiveRecorder.Flv.Pipeline.Actions;
|
||||||
using BililiveRecorder.Flv.Writer;
|
using BililiveRecorder.Flv.Writer;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user