From 93781b2a56bab3310a04cb996f217ff7c91e38de Mon Sep 17 00:00:00 2001 From: Genteure Date: Thu, 29 Apr 2021 23:51:06 +0800 Subject: [PATCH] FLV: Performance improvements (I guess) --- .../BililiveRecorder.Core.csproj | 1 + .../ProcessingRules/SplitRule.cs | 1 + .../ProcessingRules/StatsRule.cs | 35 ++- .../Recording/StandardRecordTask.cs | 1 + .../BililiveRecorder.Flv.csproj | 1 + .../Grouping/Rules/DataGroupingRule.cs | 39 ++- .../Grouping/Rules/EndGroupingRule.cs | 13 +- .../Grouping/Rules/HeaderGroupingRule.cs | 22 +- .../Grouping/Rules/ScriptGroupingRule.cs | 13 +- .../Grouping/TagGroupReader.cs | 61 ++--- BililiveRecorder.Flv/IGroupingRule.cs | 9 +- BililiveRecorder.Flv/ITagGroupReader.cs | 2 +- BililiveRecorder.Flv/LinqExtensions.cs | 36 ++- BililiveRecorder.Flv/LinqFunctions.cs | 92 +++++++ .../Pipeline/{ => Actions}/PipelineAction.cs | 2 +- .../{ => Actions}/PipelineDataAction.cs | 6 +- .../{ => Actions}/PipelineDisconnectAction.cs | 2 +- .../{ => Actions}/PipelineEndAction.cs | 2 +- .../{ => Actions}/PipelineHeaderAction.cs | 2 +- .../PipelineLogAlternativeHeaderAction.cs | 2 +- .../{ => Actions}/PipelineNewFileAction.cs | 2 +- .../{ => Actions}/PipelineScriptAction.cs | 2 +- .../Pipeline/FlvProcessingContext.cs | 2 + .../IProcessingPipelineBuilderExtensions.cs | 3 - .../Pipeline/Rules/CheckDiscontinuityRule.cs | 59 ----- .../Rules/CheckMissingKeyframeRule.cs | 47 ---- .../Rules/HandleDelayedAudioHeaderRule.cs | 46 +++- .../Pipeline/Rules/HandleEndTagRule.cs | 1 + .../Pipeline/Rules/HandleNewHeaderRule.cs | 38 +-- .../Pipeline/Rules/HandleNewScriptRule.cs | 1 + .../Rules/RemoveDuplicatedChunkRule.cs | 4 +- .../Pipeline/Rules/RemoveFillerDataRule.cs | 1 + .../Pipeline/Rules/UpdateDataTagOrderRule.cs | 36 --- .../Pipeline/Rules/UpdateTimestampJumpRule.cs | 59 ++--- .../Rules/UpdateTimestampOffsetRule.cs | 242 ++++++++---------- BililiveRecorder.Flv/TagExtentions.cs | 7 +- .../Writer/FlvProcessingContextWriter.cs | 1 + BililiveRecorder.ToolBox/Commands/Analyze.cs | 1 + BililiveRecorder.ToolBox/Commands/Fix.cs | 1 + .../ProcessingRules/StatsRule.cs | 17 +- .../Grouping/GroupingTest.cs | 1 + 41 files changed, 448 insertions(+), 465 deletions(-) create mode 100644 BililiveRecorder.Flv/LinqFunctions.cs rename BililiveRecorder.Flv/Pipeline/{ => Actions}/PipelineAction.cs (68%) rename BililiveRecorder.Flv/Pipeline/{ => Actions}/PipelineDataAction.cs (66%) rename BililiveRecorder.Flv/Pipeline/{ => Actions}/PipelineDisconnectAction.cs (83%) rename BililiveRecorder.Flv/Pipeline/{ => Actions}/PipelineEndAction.cs (87%) rename BililiveRecorder.Flv/Pipeline/{ => Actions}/PipelineHeaderAction.cs (93%) rename BililiveRecorder.Flv/Pipeline/{ => Actions}/PipelineLogAlternativeHeaderAction.cs (91%) rename BililiveRecorder.Flv/Pipeline/{ => Actions}/PipelineNewFileAction.cs (82%) rename BililiveRecorder.Flv/Pipeline/{ => Actions}/PipelineScriptAction.cs (88%) delete mode 100644 BililiveRecorder.Flv/Pipeline/Rules/CheckDiscontinuityRule.cs delete mode 100644 BililiveRecorder.Flv/Pipeline/Rules/CheckMissingKeyframeRule.cs delete mode 100644 BililiveRecorder.Flv/Pipeline/Rules/UpdateDataTagOrderRule.cs diff --git a/BililiveRecorder.Core/BililiveRecorder.Core.csproj b/BililiveRecorder.Core/BililiveRecorder.Core.csproj index 60409b2..6db3d5e 100644 --- a/BililiveRecorder.Core/BililiveRecorder.Core.csproj +++ b/BililiveRecorder.Core/BililiveRecorder.Core.csproj @@ -17,6 +17,7 @@ + diff --git a/BililiveRecorder.Core/ProcessingRules/SplitRule.cs b/BililiveRecorder.Core/ProcessingRules/SplitRule.cs index 05a59f6..a9319df 100644 --- a/BililiveRecorder.Core/ProcessingRules/SplitRule.cs +++ b/BililiveRecorder.Core/ProcessingRules/SplitRule.cs @@ -1,5 +1,6 @@ using System.Threading; using BililiveRecorder.Flv.Pipeline; +using BililiveRecorder.Flv.Pipeline.Actions; namespace BililiveRecorder.Core.ProcessingRules { diff --git a/BililiveRecorder.Core/ProcessingRules/StatsRule.cs b/BililiveRecorder.Core/ProcessingRules/StatsRule.cs index 89d1eb2..7f8c748 100644 --- a/BililiveRecorder.Core/ProcessingRules/StatsRule.cs +++ b/BililiveRecorder.Core/ProcessingRules/StatsRule.cs @@ -4,6 +4,8 @@ using System.Linq; using BililiveRecorder.Core.Event; using BililiveRecorder.Flv; using BililiveRecorder.Flv.Pipeline; +using BililiveRecorder.Flv.Pipeline.Actions; +using StructLinq; namespace BililiveRecorder.Core.ProcessingRules { @@ -32,10 +34,20 @@ namespace BililiveRecorder.Core.ProcessingRules { var e = new RecordingStatsEventArgs(); - e.TotalInputVideoByteCount = this.TotalInputVideoByteCount += e.InputVideoByteCount = - context.Actions.Where(x => x is PipelineDataAction).Cast().Sum(data => data.Tags.Where(x => x.Type == TagType.Video).Sum(x => x.Size + (11 + 4))); - e.TotalInputAudioByteCount = this.TotalInputAudioByteCount += e.InputAudioByteCount = - context.Actions.Where(x => x is PipelineDataAction).Cast().Sum(data => data.Tags.Where(x => x.Type == TagType.Audio).Sum(x => x.Size + (11 + 4))); + { + static IEnumerable FilterDataActions(IEnumerable actions) + { + 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(); @@ -81,10 +93,17 @@ namespace BililiveRecorder.Core.ProcessingRules { if (dataActions.Count > 0) { - e.TotalOutputVideoFrameCount = this.TotalOutputVideoFrameCount += e.OutputVideoFrameCount = dataActions.Sum(x => x.Tags.Count(x => x.Type == TagType.Video)); - e.TotalOutputAudioFrameCount = this.TotalOutputAudioFrameCount += e.OutputAudioFrameCount = dataActions.Sum(x => x.Tags.Count(x => x.Type == TagType.Audio)); - 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.TotalOutputVideoFrameCount = this.TotalOutputVideoFrameCount += e.OutputVideoFrameCount = + dataActions.ToStructEnumerable().Sum(ref LinqFunctions.CountVideoTags, x => x, x => x); + + 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; diff --git a/BililiveRecorder.Core/Recording/StandardRecordTask.cs b/BililiveRecorder.Core/Recording/StandardRecordTask.cs index 0de94b2..416c1ae 100644 --- a/BililiveRecorder.Core/Recording/StandardRecordTask.cs +++ b/BililiveRecorder.Core/Recording/StandardRecordTask.cs @@ -11,6 +11,7 @@ using BililiveRecorder.Core.ProcessingRules; using BililiveRecorder.Flv; using BililiveRecorder.Flv.Amf; using BililiveRecorder.Flv.Pipeline; +using BililiveRecorder.Flv.Pipeline.Actions; using Serilog; namespace BililiveRecorder.Core.Recording diff --git a/BililiveRecorder.Flv/BililiveRecorder.Flv.csproj b/BililiveRecorder.Flv/BililiveRecorder.Flv.csproj index 646df7c..fb7ec9a 100644 --- a/BililiveRecorder.Flv/BililiveRecorder.Flv.csproj +++ b/BililiveRecorder.Flv/BililiveRecorder.Flv.csproj @@ -17,6 +17,7 @@ runtime; build; native; contentfiles; analyzers + diff --git a/BililiveRecorder.Flv/Grouping/Rules/DataGroupingRule.cs b/BililiveRecorder.Flv/Grouping/Rules/DataGroupingRule.cs index 0a557c2..9d61c67 100644 --- a/BililiveRecorder.Flv/Grouping/Rules/DataGroupingRule.cs +++ b/BililiveRecorder.Flv/Grouping/Rules/DataGroupingRule.cs @@ -1,36 +1,27 @@ using System.Collections.Generic; using System.Linq; -using BililiveRecorder.Flv.Pipeline; +using BililiveRecorder.Flv.Pipeline.Actions; +using StructLinq; namespace BililiveRecorder.Flv.Grouping.Rules { public class DataGroupingRule : IGroupingRule { - public bool StartWith(Tag tag) => tag.IsData(); - - public bool AppendWith(Tag tag, LinkedList tags, out LinkedList? leftover) + private readonly struct DoesNotContainAudioData : IFunction { - var shouldAppend = - // Tag 是非关键帧数据 - 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(); - leftover.AddLast(tag); - return false; - } + public static DoesNotContainAudioData Instance; + public bool Eval(Tag element) => element.Type != TagType.Audio || element.Flag == TagFlag.Header; } - public PipelineAction CreatePipelineAction(LinkedList tags) => new PipelineDataAction(new List(tags)); + public bool CanStartWith(Tag tag) => tag.IsData(); + + public bool CanAppendWith(Tag tag, List 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 tags) => new PipelineDataAction(tags); } } diff --git a/BililiveRecorder.Flv/Grouping/Rules/EndGroupingRule.cs b/BililiveRecorder.Flv/Grouping/Rules/EndGroupingRule.cs index f408cf0..176ba84 100644 --- a/BililiveRecorder.Flv/Grouping/Rules/EndGroupingRule.cs +++ b/BililiveRecorder.Flv/Grouping/Rules/EndGroupingRule.cs @@ -1,19 +1,14 @@ using System.Collections.Generic; -using BililiveRecorder.Flv.Pipeline; +using BililiveRecorder.Flv.Pipeline.Actions; namespace BililiveRecorder.Flv.Grouping.Rules { public class EndGroupingRule : IGroupingRule { - public bool StartWith(Tag tag) => tag.IsEnd(); + public bool CanStartWith(Tag tag) => tag.IsEnd(); - public bool AppendWith(Tag tag, LinkedList tags, out LinkedList? leftover) - { - leftover = new LinkedList(); - leftover.AddLast(tag); - return false; - } + public bool CanAppendWith(Tag tag, List tags) => false; - public PipelineAction CreatePipelineAction(LinkedList tags) => new PipelineEndAction(tags.First.Value); + public PipelineAction CreatePipelineAction(List tags) => new PipelineEndAction(tags[0]); } } diff --git a/BililiveRecorder.Flv/Grouping/Rules/HeaderGroupingRule.cs b/BililiveRecorder.Flv/Grouping/Rules/HeaderGroupingRule.cs index 46b381f..ffe4097 100644 --- a/BililiveRecorder.Flv/Grouping/Rules/HeaderGroupingRule.cs +++ b/BililiveRecorder.Flv/Grouping/Rules/HeaderGroupingRule.cs @@ -1,28 +1,14 @@ using System.Collections.Generic; -using BililiveRecorder.Flv.Pipeline; +using BililiveRecorder.Flv.Pipeline.Actions; namespace BililiveRecorder.Flv.Grouping.Rules { public class HeaderGroupingRule : IGroupingRule { - public bool StartWith(Tag tag) => tag.IsHeader(); + public bool CanStartWith(Tag tag) => tag.IsHeader(); - public bool AppendWith(Tag tag, LinkedList tags, out LinkedList? leftover) - { - if (tag.IsHeader()) - { - tags.AddLast(tag); - leftover = null; - return true; - } - else - { - leftover = new LinkedList(); - leftover.AddLast(tag); - return false; - } - } + public bool CanAppendWith(Tag tag, List tags) => tag.IsHeader(); - public PipelineAction CreatePipelineAction(LinkedList tags) => new PipelineHeaderAction(new List(tags)); + public PipelineAction CreatePipelineAction(List tags) => new PipelineHeaderAction(tags); } } diff --git a/BililiveRecorder.Flv/Grouping/Rules/ScriptGroupingRule.cs b/BililiveRecorder.Flv/Grouping/Rules/ScriptGroupingRule.cs index 5166fbd..08d7af6 100644 --- a/BililiveRecorder.Flv/Grouping/Rules/ScriptGroupingRule.cs +++ b/BililiveRecorder.Flv/Grouping/Rules/ScriptGroupingRule.cs @@ -1,19 +1,14 @@ using System.Collections.Generic; -using BililiveRecorder.Flv.Pipeline; +using BililiveRecorder.Flv.Pipeline.Actions; namespace BililiveRecorder.Flv.Grouping.Rules { public class ScriptGroupingRule : IGroupingRule { - public bool StartWith(Tag tag) => tag.IsScript(); + public bool CanStartWith(Tag tag) => tag.IsScript(); - public bool AppendWith(Tag tag, LinkedList tags, out LinkedList? leftover) - { - leftover = new LinkedList(); - leftover.AddLast(tag); - return false; - } + public bool CanAppendWith(Tag tag, List tags) => false; - public PipelineAction CreatePipelineAction(LinkedList tags) => new PipelineScriptAction(tags.First.Value); + public PipelineAction CreatePipelineAction(List tags) => new PipelineScriptAction(tags[0]); } } diff --git a/BililiveRecorder.Flv/Grouping/TagGroupReader.cs b/BililiveRecorder.Flv/Grouping/TagGroupReader.cs index 279f957..274219c 100644 --- a/BililiveRecorder.Flv/Grouping/TagGroupReader.cs +++ b/BililiveRecorder.Flv/Grouping/TagGroupReader.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using BililiveRecorder.Flv.Grouping.Rules; -using BililiveRecorder.Flv.Pipeline; +using BililiveRecorder.Flv.Pipeline.Actions; namespace BililiveRecorder.Flv.Grouping { @@ -14,7 +13,7 @@ namespace BililiveRecorder.Flv.Grouping private readonly bool leaveOpen; private bool disposedValue; - private LinkedList? leftover; + private Tag? leftoverTag; public IFlvTagReader TagReader { get; } public IList GroupingRules { get; } @@ -45,16 +44,12 @@ namespace BililiveRecorder.Flv.Grouping } try { - LinkedList? queue = null; Tag? firstTag; - if (this.leftover is not null && this.leftover.Count > 0) + if (this.leftoverTag is not null) { - queue = this.leftover; - this.leftover = null; - - firstTag = queue.First.Value; - queue.RemoveFirst(); + firstTag = this.leftoverTag; + this.leftoverTag = null; } else { @@ -65,38 +60,44 @@ namespace BililiveRecorder.Flv.Grouping 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) throw new Exception("No grouping rule accepting tags: " + firstTag.ToString()); - var tags = new LinkedList(); - tags.AddLast(firstTag); + var tags = new List(32) + { + firstTag + }; + + firstTag = null; 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; - queue.RemoveFirst(); + tags.Add(tag); } else { - tag = await this.TagReader.ReadTagAsync(token).ConfigureAwait(false); - } - - 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); - } + // 如果数据已经读完,或当前规则不接受此 Tag + this.leftoverTag = tag; break; } } diff --git a/BililiveRecorder.Flv/IGroupingRule.cs b/BililiveRecorder.Flv/IGroupingRule.cs index 60b273f..075eb82 100644 --- a/BililiveRecorder.Flv/IGroupingRule.cs +++ b/BililiveRecorder.Flv/IGroupingRule.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using BililiveRecorder.Flv.Pipeline; +using BililiveRecorder.Flv.Pipeline.Actions; namespace BililiveRecorder.Flv { @@ -10,17 +10,16 @@ namespace BililiveRecorder.Flv /// /// Current Tags /// - bool StartWith(Tag tag); + bool CanStartWith(Tag tag); /// /// /// /// Tag not yet added to the list /// List of tags - /// /// - bool AppendWith(Tag tag, LinkedList tags, out LinkedList? leftover); + bool CanAppendWith(Tag tag, List tags); - PipelineAction CreatePipelineAction(LinkedList tags); + PipelineAction CreatePipelineAction(List tags); } } diff --git a/BililiveRecorder.Flv/ITagGroupReader.cs b/BililiveRecorder.Flv/ITagGroupReader.cs index 56cfea0..96dbd28 100644 --- a/BililiveRecorder.Flv/ITagGroupReader.cs +++ b/BililiveRecorder.Flv/ITagGroupReader.cs @@ -1,7 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; -using BililiveRecorder.Flv.Pipeline; +using BililiveRecorder.Flv.Pipeline.Actions; namespace BililiveRecorder.Flv { diff --git a/BililiveRecorder.Flv/LinqExtensions.cs b/BililiveRecorder.Flv/LinqExtensions.cs index d5d3154..34cda85 100644 --- a/BililiveRecorder.Flv/LinqExtensions.cs +++ b/BililiveRecorder.Flv/LinqExtensions.cs @@ -14,18 +14,46 @@ namespace BililiveRecorder.Flv if (!iterator.MoveNext()) return false; - var lastItem = iterator.Current; + var previousItem = iterator.Current; while (iterator.MoveNext()) { - var current = iterator.Current; + var currentItem = iterator.Current; - if (predicate(lastItem, current)) + if (predicate(previousItem, currentItem)) return true; - lastItem = current; + previousItem = currentItem; + } + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Any2(this IEnumerable source, ref TFunction function) + where TFunction : ITwoInputFunction + { + 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; } } + + public interface ITwoInputFunction + { + TOut Eval(TIn a, TIn b); + } } diff --git a/BililiveRecorder.Flv/LinqFunctions.cs b/BililiveRecorder.Flv/LinqFunctions.cs new file mode 100644 index 0000000..4ebeb70 --- /dev/null +++ b/BililiveRecorder.Flv/LinqFunctions.cs @@ -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, IInFunction + { + public bool Eval(Tag element) => element.IsHeader(); + public bool Eval(in Tag element) => element.IsHeader(); + } + + public static TagIsDataStruct TagIsData; + public readonly struct TagIsDataStruct : IFunction, IInFunction + { + public bool Eval(Tag element) => element.IsData(); + public bool Eval(in Tag element) => element.IsData(); + } + + public static TagIsVideoStruct TagIsVideo; + public readonly struct TagIsVideoStruct : IFunction, IInFunction + { + 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, IInFunction + { + 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, IInFunction + { + 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, IInFunction + { + 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, IInFunction + { + 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, IInFunction + { + 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, IInFunction + { + 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, IInFunction + { + 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, IInFunction + { + 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, IInFunction + { + 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(); + } + } +} diff --git a/BililiveRecorder.Flv/Pipeline/PipelineAction.cs b/BililiveRecorder.Flv/Pipeline/Actions/PipelineAction.cs similarity index 68% rename from BililiveRecorder.Flv/Pipeline/PipelineAction.cs rename to BililiveRecorder.Flv/Pipeline/Actions/PipelineAction.cs index 3ebfced..5323cb9 100644 --- a/BililiveRecorder.Flv/Pipeline/PipelineAction.cs +++ b/BililiveRecorder.Flv/Pipeline/Actions/PipelineAction.cs @@ -1,4 +1,4 @@ -namespace BililiveRecorder.Flv.Pipeline +namespace BililiveRecorder.Flv.Pipeline.Actions { public abstract class PipelineAction { diff --git a/BililiveRecorder.Flv/Pipeline/PipelineDataAction.cs b/BililiveRecorder.Flv/Pipeline/Actions/PipelineDataAction.cs similarity index 66% rename from BililiveRecorder.Flv/Pipeline/PipelineDataAction.cs rename to BililiveRecorder.Flv/Pipeline/Actions/PipelineDataAction.cs index eed121a..925ef11 100644 --- a/BililiveRecorder.Flv/Pipeline/PipelineDataAction.cs +++ b/BililiveRecorder.Flv/Pipeline/Actions/PipelineDataAction.cs @@ -1,16 +1,16 @@ using System; using System.Collections.Generic; -namespace BililiveRecorder.Flv.Pipeline +namespace BililiveRecorder.Flv.Pipeline.Actions { public class PipelineDataAction : PipelineAction { - public PipelineDataAction(List tags) + public PipelineDataAction(IReadOnlyList tags) { this.Tags = tags ?? throw new ArgumentNullException(nameof(tags)); } - public List Tags { get; set; } + public IReadOnlyList Tags { get; set; } public override PipelineAction Clone() => new PipelineDataAction(new List(this.Tags)); } diff --git a/BililiveRecorder.Flv/Pipeline/PipelineDisconnectAction.cs b/BililiveRecorder.Flv/Pipeline/Actions/PipelineDisconnectAction.cs similarity index 83% rename from BililiveRecorder.Flv/Pipeline/PipelineDisconnectAction.cs rename to BililiveRecorder.Flv/Pipeline/Actions/PipelineDisconnectAction.cs index 5c1c6c1..fb94a17 100644 --- a/BililiveRecorder.Flv/Pipeline/PipelineDisconnectAction.cs +++ b/BililiveRecorder.Flv/Pipeline/Actions/PipelineDisconnectAction.cs @@ -1,4 +1,4 @@ -namespace BililiveRecorder.Flv.Pipeline +namespace BililiveRecorder.Flv.Pipeline.Actions { public class PipelineDisconnectAction : PipelineAction { diff --git a/BililiveRecorder.Flv/Pipeline/PipelineEndAction.cs b/BililiveRecorder.Flv/Pipeline/Actions/PipelineEndAction.cs similarity index 87% rename from BililiveRecorder.Flv/Pipeline/PipelineEndAction.cs rename to BililiveRecorder.Flv/Pipeline/Actions/PipelineEndAction.cs index 6eccd5c..733123e 100644 --- a/BililiveRecorder.Flv/Pipeline/PipelineEndAction.cs +++ b/BililiveRecorder.Flv/Pipeline/Actions/PipelineEndAction.cs @@ -1,4 +1,4 @@ -namespace BililiveRecorder.Flv.Pipeline +namespace BililiveRecorder.Flv.Pipeline.Actions { public class PipelineEndAction : PipelineAction { diff --git a/BililiveRecorder.Flv/Pipeline/PipelineHeaderAction.cs b/BililiveRecorder.Flv/Pipeline/Actions/PipelineHeaderAction.cs similarity index 93% rename from BililiveRecorder.Flv/Pipeline/PipelineHeaderAction.cs rename to BililiveRecorder.Flv/Pipeline/Actions/PipelineHeaderAction.cs index 53ddd7d..377fb53 100644 --- a/BililiveRecorder.Flv/Pipeline/PipelineHeaderAction.cs +++ b/BililiveRecorder.Flv/Pipeline/Actions/PipelineHeaderAction.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; -namespace BililiveRecorder.Flv.Pipeline +namespace BililiveRecorder.Flv.Pipeline.Actions { public class PipelineHeaderAction : PipelineAction { diff --git a/BililiveRecorder.Flv/Pipeline/PipelineLogAlternativeHeaderAction.cs b/BililiveRecorder.Flv/Pipeline/Actions/PipelineLogAlternativeHeaderAction.cs similarity index 91% rename from BililiveRecorder.Flv/Pipeline/PipelineLogAlternativeHeaderAction.cs rename to BililiveRecorder.Flv/Pipeline/Actions/PipelineLogAlternativeHeaderAction.cs index f141376..d67d2b5 100644 --- a/BililiveRecorder.Flv/Pipeline/PipelineLogAlternativeHeaderAction.cs +++ b/BililiveRecorder.Flv/Pipeline/Actions/PipelineLogAlternativeHeaderAction.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; -namespace BililiveRecorder.Flv.Pipeline +namespace BililiveRecorder.Flv.Pipeline.Actions { public class PipelineLogAlternativeHeaderAction : PipelineAction { diff --git a/BililiveRecorder.Flv/Pipeline/PipelineNewFileAction.cs b/BililiveRecorder.Flv/Pipeline/Actions/PipelineNewFileAction.cs similarity index 82% rename from BililiveRecorder.Flv/Pipeline/PipelineNewFileAction.cs rename to BililiveRecorder.Flv/Pipeline/Actions/PipelineNewFileAction.cs index 76f8584..dd72428 100644 --- a/BililiveRecorder.Flv/Pipeline/PipelineNewFileAction.cs +++ b/BililiveRecorder.Flv/Pipeline/Actions/PipelineNewFileAction.cs @@ -1,4 +1,4 @@ -namespace BililiveRecorder.Flv.Pipeline +namespace BililiveRecorder.Flv.Pipeline.Actions { public class PipelineNewFileAction : PipelineAction { diff --git a/BililiveRecorder.Flv/Pipeline/PipelineScriptAction.cs b/BililiveRecorder.Flv/Pipeline/Actions/PipelineScriptAction.cs similarity index 88% rename from BililiveRecorder.Flv/Pipeline/PipelineScriptAction.cs rename to BililiveRecorder.Flv/Pipeline/Actions/PipelineScriptAction.cs index a8d3089..b7ca64f 100644 --- a/BililiveRecorder.Flv/Pipeline/PipelineScriptAction.cs +++ b/BililiveRecorder.Flv/Pipeline/Actions/PipelineScriptAction.cs @@ -1,6 +1,6 @@ using System; -namespace BililiveRecorder.Flv.Pipeline +namespace BililiveRecorder.Flv.Pipeline.Actions { public class PipelineScriptAction : PipelineAction { diff --git a/BililiveRecorder.Flv/Pipeline/FlvProcessingContext.cs b/BililiveRecorder.Flv/Pipeline/FlvProcessingContext.cs index 66a1d97..905bbcf 100644 --- a/BililiveRecorder.Flv/Pipeline/FlvProcessingContext.cs +++ b/BililiveRecorder.Flv/Pipeline/FlvProcessingContext.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using BililiveRecorder.Flv.Pipeline.Actions; +using StructLinq; namespace BililiveRecorder.Flv.Pipeline { diff --git a/BililiveRecorder.Flv/Pipeline/IProcessingPipelineBuilderExtensions.cs b/BililiveRecorder.Flv/Pipeline/IProcessingPipelineBuilderExtensions.cs index 6193076..e96c1ca 100644 --- a/BililiveRecorder.Flv/Pipeline/IProcessingPipelineBuilderExtensions.cs +++ b/BililiveRecorder.Flv/Pipeline/IProcessingPipelineBuilderExtensions.cs @@ -26,9 +26,6 @@ namespace BililiveRecorder.Flv.Pipeline builder .Add() .Add() - // TODO .Add() - // .Add() - // .Add() .Add() .Add() .Add() diff --git a/BililiveRecorder.Flv/Pipeline/Rules/CheckDiscontinuityRule.cs b/BililiveRecorder.Flv/Pipeline/Rules/CheckDiscontinuityRule.cs deleted file mode 100644 index a4f224d..0000000 --- a/BililiveRecorder.Flv/Pipeline/Rules/CheckDiscontinuityRule.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BililiveRecorder.Flv.Pipeline.Rules -{ - /// - /// 检查分块内时间戳问题 - /// - /// - /// 到目前为止还未发现有在一个 GOP 内出现时间戳异常问题
- /// 本规则是为了预防实际使用中遇到意外情况
- ///
- /// 本规则应该放在所有规则前面 - ///
- 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 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; - } - } -} diff --git a/BililiveRecorder.Flv/Pipeline/Rules/CheckMissingKeyframeRule.cs b/BililiveRecorder.Flv/Pipeline/Rules/CheckMissingKeyframeRule.cs deleted file mode 100644 index 5f42f0b..0000000 --- a/BililiveRecorder.Flv/Pipeline/Rules/CheckMissingKeyframeRule.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace BililiveRecorder.Flv.Pipeline.Rules -{ - /// - /// 检查缺少关键帧的问题 - /// - /// - /// 到目前为止还未发现有出现过此问题
- /// 本规则是为了预防实际使用中遇到意外情况
- ///
- /// 本规则应该放在所有规则前面 - ///
- 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 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; - } - } -} diff --git a/BililiveRecorder.Flv/Pipeline/Rules/HandleDelayedAudioHeaderRule.cs b/BililiveRecorder.Flv/Pipeline/Rules/HandleDelayedAudioHeaderRule.cs index 6df8b86..6a32588 100644 --- a/BililiveRecorder.Flv/Pipeline/Rules/HandleDelayedAudioHeaderRule.cs +++ b/BililiveRecorder.Flv/Pipeline/Rules/HandleDelayedAudioHeaderRule.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using BililiveRecorder.Flv.Pipeline.Actions; +using StructLinq; namespace BililiveRecorder.Flv.Pipeline.Rules { @@ -12,7 +14,8 @@ namespace BililiveRecorder.Flv.Pipeline.Rules /// 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) { @@ -25,25 +28,42 @@ namespace BililiveRecorder.Flv.Pipeline.Rules if (action is PipelineDataAction data) { 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 之前出现了音频数据 - yield return PipelineDisconnectAction.Instance; - yield return null; - yield break; + if (tags[i].Type == TagType.Audio) + { + if (tags[i].Flag != TagFlag.None) + { + // 发现了 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 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); yield return newHeaderAction; diff --git a/BililiveRecorder.Flv/Pipeline/Rules/HandleEndTagRule.cs b/BililiveRecorder.Flv/Pipeline/Rules/HandleEndTagRule.cs index 1a2a63e..66e1d85 100644 --- a/BililiveRecorder.Flv/Pipeline/Rules/HandleEndTagRule.cs +++ b/BililiveRecorder.Flv/Pipeline/Rules/HandleEndTagRule.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using BililiveRecorder.Flv.Pipeline.Actions; namespace BililiveRecorder.Flv.Pipeline.Rules { diff --git a/BililiveRecorder.Flv/Pipeline/Rules/HandleNewHeaderRule.cs b/BililiveRecorder.Flv/Pipeline/Rules/HandleNewHeaderRule.cs index 063043a..a59d14e 100644 --- a/BililiveRecorder.Flv/Pipeline/Rules/HandleNewHeaderRule.cs +++ b/BililiveRecorder.Flv/Pipeline/Rules/HandleNewHeaderRule.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using BililiveRecorder.Flv.Pipeline.Actions; +using StructLinq; +using StructLinq.Where; namespace BililiveRecorder.Flv.Pipeline.Rules { @@ -40,8 +43,8 @@ namespace BililiveRecorder.Flv.Pipeline.Rules // 音频 视频 分别单独处理 var group = header.AllTags.GroupBy(x => x.Type); - var currentVideoHeader = SelectHeader(ref multiple_header_present, group.FirstOrDefault(x => x.Key == TagType.Video)); - var currentAudioHeader = SelectHeader(ref multiple_header_present, group.FirstOrDefault(x => x.Key == TagType.Audio)); + var currentVideoHeader = SelectHeader(ref multiple_header_present, header.AllTags.ToStructEnumerable().Where(ref LinqFunctions.TagIsVideo, x => x)); + var currentAudioHeader = SelectHeader(ref multiple_header_present, header.AllTags.ToStructEnumerable().Where(ref LinqFunctions.TagIsAudio, x => x)); if (multiple_header_present) context.AddComment(MultipleHeaderComment); @@ -91,30 +94,29 @@ namespace BililiveRecorder.Flv.Pipeline.Rules yield return action; } - private static Tag? SelectHeader(ref bool multiple_header_present, IGrouping tagGroup) + private static Tag? SelectHeader(ref bool multiple_header_present, WhereEnumerable tags) + where TEnumerable : struct, IStructEnumerable + where TEnumerator : struct, IStructEnumerator + where TFunction : struct, IFunction { Tag? currentHeader; - if (tagGroup != null) + + if (tags.Count(x => x) > 1) { // 检查是否存在 **多个** **不同的** Header - if (tagGroup.Count() > 1) - { - var first = tagGroup.First(); + var first = tags.First(x => x); - if (tagGroup.Skip(1).All(x => first.BinaryData?.SequenceEqual(x.BinaryData) ?? false)) - currentHeader = first; - else - { - // 默认最后一个为正确的 - currentHeader = tagGroup.Last(); - multiple_header_present = true; - } - } + if (tags.Skip(1, x => x).All(x => first.BinaryData?.SequenceEqual(x.BinaryData) ?? false)) + currentHeader = first; else - currentHeader = tagGroup.FirstOrDefault(); + { + // 默认最后一个为正确的 + currentHeader = tags.Last(x => x); + multiple_header_present = true; + } } else - currentHeader = null; + currentHeader = tags.FirstOrDefault(x => x); return currentHeader; } diff --git a/BililiveRecorder.Flv/Pipeline/Rules/HandleNewScriptRule.cs b/BililiveRecorder.Flv/Pipeline/Rules/HandleNewScriptRule.cs index 36aab19..61c43da 100644 --- a/BililiveRecorder.Flv/Pipeline/Rules/HandleNewScriptRule.cs +++ b/BililiveRecorder.Flv/Pipeline/Rules/HandleNewScriptRule.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using BililiveRecorder.Flv.Amf; +using BililiveRecorder.Flv.Pipeline.Actions; namespace BililiveRecorder.Flv.Pipeline.Rules { diff --git a/BililiveRecorder.Flv/Pipeline/Rules/RemoveDuplicatedChunkRule.cs b/BililiveRecorder.Flv/Pipeline/Rules/RemoveDuplicatedChunkRule.cs index 53a3469..8c357a5 100644 --- a/BililiveRecorder.Flv/Pipeline/Rules/RemoveDuplicatedChunkRule.cs +++ b/BililiveRecorder.Flv/Pipeline/Rules/RemoveDuplicatedChunkRule.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.Linq; using BililiveRecorder.Flv; +using BililiveRecorder.Flv.Pipeline.Actions; +using StructLinq; 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); } diff --git a/BililiveRecorder.Flv/Pipeline/Rules/RemoveFillerDataRule.cs b/BililiveRecorder.Flv/Pipeline/Rules/RemoveFillerDataRule.cs index e57a5b2..77cd83f 100644 --- a/BililiveRecorder.Flv/Pipeline/Rules/RemoveFillerDataRule.cs +++ b/BililiveRecorder.Flv/Pipeline/Rules/RemoveFillerDataRule.cs @@ -1,4 +1,5 @@ using System; +using BililiveRecorder.Flv.Pipeline.Actions; namespace BililiveRecorder.Flv.Pipeline.Rules { diff --git a/BililiveRecorder.Flv/Pipeline/Rules/UpdateDataTagOrderRule.cs b/BililiveRecorder.Flv/Pipeline/Rules/UpdateDataTagOrderRule.cs deleted file mode 100644 index db93304..0000000 --- a/BililiveRecorder.Flv/Pipeline/Rules/UpdateDataTagOrderRule.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace BililiveRecorder.Flv.Pipeline.Rules -{ - /// - /// 修复 Tag 错位等时间戳相关问题 - /// - public class UpdateDataTagOrderRule : ISimpleProcessingRule - { - public void Run(FlvProcessingContext context, Action next) - { - context.PerActionRun(this.RunPerAction); - next(); - } - - private IEnumerable 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; - } - } -} diff --git a/BililiveRecorder.Flv/Pipeline/Rules/UpdateTimestampJumpRule.cs b/BililiveRecorder.Flv/Pipeline/Rules/UpdateTimestampJumpRule.cs index 2babbef..9ed3299 100644 --- a/BililiveRecorder.Flv/Pipeline/Rules/UpdateTimestampJumpRule.cs +++ b/BililiveRecorder.Flv/Pipeline/Rules/UpdateTimestampJumpRule.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; +using BililiveRecorder.Flv.Pipeline.Actions; +using StructLinq; namespace BililiveRecorder.Flv.Pipeline.Rules { @@ -28,8 +31,27 @@ namespace BililiveRecorder.Flv.Pipeline.Rules foreach (var action in context.Actions) { if (action is PipelineDataAction dataAction) - { - this.SetDataTimestamp(dataAction.Tags, ts, context); + { // SetDataTimestamp + 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) { @@ -64,45 +86,24 @@ namespace BililiveRecorder.Flv.Pipeline.Rules } } - private void SetDataTimestamp(IReadOnlyList tags, TimestampStore ts, FlvProcessingContext context) - { - 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); - } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] private int CalculateNewTarget(IReadOnlyList tags) { // 有可能出现只有音频或只有视频的情况 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); - 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); return Math.Max(video, audio); + [MethodImpl(MethodImplOptions.AggressiveInlining)] static int CalculatePerChannel(IReadOnlyList 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; if (sample.Length != 2) { @@ -116,7 +117,7 @@ namespace BililiveRecorder.Flv.Pipeline.Rules durationPerTag = fallback; } - return durationPerTag + tags.Last(x => x.Type == type).Timestamp; + return durationPerTag + tags.ToStructEnumerable().Last(x => x.Type == type).Timestamp; } } diff --git a/BililiveRecorder.Flv/Pipeline/Rules/UpdateTimestampOffsetRule.cs b/BililiveRecorder.Flv/Pipeline/Rules/UpdateTimestampOffsetRule.cs index 71da72e..82e7ed6 100644 --- a/BililiveRecorder.Flv/Pipeline/Rules/UpdateTimestampOffsetRule.cs +++ b/BililiveRecorder.Flv/Pipeline/Rules/UpdateTimestampOffsetRule.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; +using BililiveRecorder.Flv.Pipeline.Actions; +using StructLinq; namespace BililiveRecorder.Flv.Pipeline.Rules { @@ -15,20 +18,25 @@ namespace BililiveRecorder.Flv.Pipeline.Rules next(); } - private bool CheckIfNormal(IEnumerable data) => !data.Any2((a, b) => a.Timestamp > b.Timestamp); + private readonly struct IsNotNormal : ITwoInputFunction + { + public static IsNotNormal Instance; + public bool Eval(Tag a, Tag b) => a.Timestamp > b.Timestamp; + } private IEnumerable RunPerAction(FlvProcessingContext context, PipelineAction action) { if (action is PipelineDataAction data) { - var isNormal = this.CheckIfNormal(data.Tags); - if (isNormal) + var isNotNormal = data.Tags.Any2(ref IsNotNormal.Instance); + if (!isNotNormal) { + // 没有问题 yield return data; 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; @@ -37,146 +45,118 @@ namespace BililiveRecorder.Flv.Pipeline.Rules } else { - var oc = new OffsetCalculator(); + /* + * 设定做调整的为视频帧,参照每个视频帧左右(左为前、右为后)的音频帧的时间戳 + * 计算出最多和最少能符合“不小于前面的帧并且不大于后面的帧”的要求的偏移量 + * 如果当前偏移量比总偏移量要求更严,则使用当前偏移量范围作为总偏移量范围 + * */ + var offset = 0; - foreach (var tag in data.Tags) - oc.AddTag(tag); - - if (oc.Calculate(out var videoOffset)) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void ReduceOffsetRange(ref int maxOffset, ref int minOffset, Tag? leftAudio, Tag? rightAudio, Stack tags) { - 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 (tag.Type == TagType.Video) - tag.Timestamp += videoOffset; + if (leftAudio is not null) + { + 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; - yield break; + Tag? lastAudio = null; + var tags = new Stack(); + 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 - { - context.AddComment(comment2); - yield return PipelineDisconnectAction.Instance; - yield break; + { // 范围无效 + goto invalidOffset; } + + 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 yield return action; } - - /// - /// 音视频偏差量计算 - /// - private class OffsetCalculator - { - /* 算法思路和原理 - * 设定作调整的为视频帧,参照每个视频帧左右(左为前、右为后)的音频帧的时间戳 - * 计算出最多和最少能符合“不小于前面的帧并且不大于后面的帧”的要求的偏移量 - * 如果当前偏移量比总偏移量要求更严,则使用当前偏移量范围作为总偏移量范围 - * */ - - private Tag? lastAudio = null; - private readonly Stack tags = new Stack(); - - 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; - } - } - } - } } } diff --git a/BililiveRecorder.Flv/TagExtentions.cs b/BililiveRecorder.Flv/TagExtentions.cs index e140450..f7bcbc4 100644 --- a/BililiveRecorder.Flv/TagExtentions.cs +++ b/BililiveRecorder.Flv/TagExtentions.cs @@ -15,8 +15,8 @@ namespace BililiveRecorder.Flv [MethodImpl(MethodImplOptions.AggressiveInlining)] 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)] public static bool IsEnd(this Tag tag) @@ -24,7 +24,8 @@ namespace BililiveRecorder.Flv [MethodImpl(MethodImplOptions.AggressiveInlining)] 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)] public static bool IsNonKeyframeData(this Tag tag) diff --git a/BililiveRecorder.Flv/Writer/FlvProcessingContextWriter.cs b/BililiveRecorder.Flv/Writer/FlvProcessingContextWriter.cs index 2d54b75..2b1b2f4 100644 --- a/BililiveRecorder.Flv/Writer/FlvProcessingContextWriter.cs +++ b/BililiveRecorder.Flv/Writer/FlvProcessingContextWriter.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using BililiveRecorder.Flv.Amf; using BililiveRecorder.Flv.Pipeline; +using BililiveRecorder.Flv.Pipeline.Actions; namespace BililiveRecorder.Flv.Writer { diff --git a/BililiveRecorder.ToolBox/Commands/Analyze.cs b/BililiveRecorder.ToolBox/Commands/Analyze.cs index db72ba9..6e345a0 100644 --- a/BililiveRecorder.ToolBox/Commands/Analyze.cs +++ b/BililiveRecorder.ToolBox/Commands/Analyze.cs @@ -10,6 +10,7 @@ using BililiveRecorder.Flv.Amf; using BililiveRecorder.Flv.Grouping; using BililiveRecorder.Flv.Parser; using BililiveRecorder.Flv.Pipeline; +using BililiveRecorder.Flv.Pipeline.Actions; using BililiveRecorder.Flv.Writer; using BililiveRecorder.Flv.Xml; using BililiveRecorder.ToolBox.ProcessingRules; diff --git a/BililiveRecorder.ToolBox/Commands/Fix.cs b/BililiveRecorder.ToolBox/Commands/Fix.cs index 490127f..e8080c5 100644 --- a/BililiveRecorder.ToolBox/Commands/Fix.cs +++ b/BililiveRecorder.ToolBox/Commands/Fix.cs @@ -9,6 +9,7 @@ using BililiveRecorder.Flv; using BililiveRecorder.Flv.Grouping; using BililiveRecorder.Flv.Parser; using BililiveRecorder.Flv.Pipeline; +using BililiveRecorder.Flv.Pipeline.Actions; using BililiveRecorder.Flv.Writer; using BililiveRecorder.Flv.Xml; using BililiveRecorder.ToolBox.ProcessingRules; diff --git a/BililiveRecorder.ToolBox/ProcessingRules/StatsRule.cs b/BililiveRecorder.ToolBox/ProcessingRules/StatsRule.cs index e8a420f..3dc9a34 100644 --- a/BililiveRecorder.ToolBox/ProcessingRules/StatsRule.cs +++ b/BililiveRecorder.ToolBox/ProcessingRules/StatsRule.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; using BililiveRecorder.Flv; using BililiveRecorder.Flv.Pipeline; +using BililiveRecorder.Flv.Pipeline.Actions; +using StructLinq; namespace BililiveRecorder.ToolBox.ProcessingRules { @@ -40,13 +42,14 @@ namespace BililiveRecorder.ToolBox.ProcessingRules var stat = new FlvStats { FrameCount = timestamps.Count, - FrameDurations = timestamps.Select((time, i) => i == 0 ? 0 : time - timestamps[i - 1]) - .Skip(1) - .GroupBy(x => x) - .ToDictionary(x => x.Key, x => x.Count()) - .OrderByDescending(x => x.Value) - .ThenByDescending(x => x.Key) - .ToDictionary(x => x.Key, x => x.Value) + FrameDurations = timestamps + .Select((time, i) => i == 0 ? 0 : time - timestamps[i - 1]) + .Skip(1) + .GroupBy(x => x) + .ToDictionary(x => x.Key, x => x.Count()) + .OrderByDescending(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(); diff --git a/test/BililiveRecorder.Flv.UnitTests/Grouping/GroupingTest.cs b/test/BililiveRecorder.Flv.UnitTests/Grouping/GroupingTest.cs index 8593a86..5c42bdb 100644 --- a/test/BililiveRecorder.Flv.UnitTests/Grouping/GroupingTest.cs +++ b/test/BililiveRecorder.Flv.UnitTests/Grouping/GroupingTest.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using BililiveRecorder.Flv.Grouping; using BililiveRecorder.Flv.Parser; using BililiveRecorder.Flv.Pipeline; +using BililiveRecorder.Flv.Pipeline.Actions; using BililiveRecorder.Flv.Writer; using Microsoft.Extensions.DependencyInjection; using Xunit;