diff --git a/BililiveRecorder.Cli/Configure/ConfigInstructions.gen.cs b/BililiveRecorder.Cli/Configure/ConfigInstructions.gen.cs index 497d499..3d0fa38 100644 --- a/BililiveRecorder.Cli/Configure/ConfigInstructions.gen.cs +++ b/BililiveRecorder.Cli/Configure/ConfigInstructions.gen.cs @@ -34,7 +34,8 @@ namespace BililiveRecorder.Cli.Configure TimingStreamConnect, TimingDanmakuRetry, TimingWatchdogTimeout, - RecordDanmakuFlushInterval + RecordDanmakuFlushInterval, + FlvMetadataKeyframeIndexCount } public enum RoomConfigProperties { @@ -81,6 +82,7 @@ namespace BililiveRecorder.Cli.Configure GlobalConfig.Add(GlobalConfigProperties.TimingDanmakuRetry, new ConfigInstruction(config => config.HasTimingDanmakuRetry = false, (config, value) => config.TimingDanmakuRetry = value) { Name = "TimingDanmakuRetry", CanBeOptional = true }); GlobalConfig.Add(GlobalConfigProperties.TimingWatchdogTimeout, new ConfigInstruction(config => config.HasTimingWatchdogTimeout = false, (config, value) => config.TimingWatchdogTimeout = value) { Name = "TimingWatchdogTimeout", CanBeOptional = true }); GlobalConfig.Add(GlobalConfigProperties.RecordDanmakuFlushInterval, new ConfigInstruction(config => config.HasRecordDanmakuFlushInterval = false, (config, value) => config.RecordDanmakuFlushInterval = value) { Name = "RecordDanmakuFlushInterval", CanBeOptional = true }); + GlobalConfig.Add(GlobalConfigProperties.FlvMetadataKeyframeIndexCount, new ConfigInstruction(config => config.HasFlvMetadataKeyframeIndexCount = false, (config, value) => config.FlvMetadataKeyframeIndexCount = value) { Name = "FlvMetadataKeyframeIndexCount", CanBeOptional = true }); RoomConfig.Add(RoomConfigProperties.RoomId, new ConfigInstruction(config => config.HasRoomId = false, (config, value) => config.RoomId = value) { Name = "RoomId", CanBeOptional = false }); RoomConfig.Add(RoomConfigProperties.AutoRecord, new ConfigInstruction(config => config.HasAutoRecord = false, (config, value) => config.AutoRecord = value) { Name = "AutoRecord", CanBeOptional = false }); diff --git a/BililiveRecorder.Core/Config/V2/Config.gen.cs b/BililiveRecorder.Core/Config/V2/Config.gen.cs index bbd44bc..805fe78 100644 --- a/BililiveRecorder.Core/Config/V2/Config.gen.cs +++ b/BililiveRecorder.Core/Config/V2/Config.gen.cs @@ -166,6 +166,11 @@ namespace BililiveRecorder.Core.Config.V2 /// public uint RecordDanmakuFlushInterval => this.GetPropertyValue(); + /// + /// FLV文件关键帧索引数量 + /// + public uint FlvMetadataKeyframeIndexCount => this.GetPropertyValue(); + } [JsonObject(MemberSerialization.OptIn)] @@ -347,6 +352,14 @@ namespace BililiveRecorder.Core.Config.V2 [JsonProperty(nameof(RecordDanmakuFlushInterval)), EditorBrowsable(EditorBrowsableState.Never)] public Optional OptionalRecordDanmakuFlushInterval { get => this.GetPropertyValueOptional(nameof(this.RecordDanmakuFlushInterval)); set => this.SetPropertyValueOptional(value, nameof(this.RecordDanmakuFlushInterval)); } + /// + /// FLV文件关键帧索引数量 + /// + public uint FlvMetadataKeyframeIndexCount { get => this.GetPropertyValue(); set => this.SetPropertyValue(value); } + public bool HasFlvMetadataKeyframeIndexCount { get => this.GetPropertyHasValue(nameof(this.FlvMetadataKeyframeIndexCount)); set => this.SetPropertyHasValue(value, nameof(this.FlvMetadataKeyframeIndexCount)); } + [JsonProperty(nameof(FlvMetadataKeyframeIndexCount)), EditorBrowsable(EditorBrowsableState.Never)] + public Optional OptionalFlvMetadataKeyframeIndexCount { get => this.GetPropertyValueOptional(nameof(this.FlvMetadataKeyframeIndexCount)); set => this.SetPropertyValueOptional(value, nameof(this.FlvMetadataKeyframeIndexCount)); } + } public sealed partial class DefaultConfig @@ -398,6 +411,8 @@ namespace BililiveRecorder.Core.Config.V2 public uint RecordDanmakuFlushInterval => 20; + public uint FlvMetadataKeyframeIndexCount => 6300; + } } diff --git a/BililiveRecorder.Core/Recording/FlvProcessingContextWriterWithFileWriterFactory.cs b/BililiveRecorder.Core/Recording/FlvProcessingContextWriterWithFileWriterFactory.cs index cb26ff4..98484f8 100644 --- a/BililiveRecorder.Core/Recording/FlvProcessingContextWriterWithFileWriterFactory.cs +++ b/BililiveRecorder.Core/Recording/FlvProcessingContextWriterWithFileWriterFactory.cs @@ -15,12 +15,12 @@ namespace BililiveRecorder.Core.Recording this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); } - public IFlvProcessingContextWriter CreateWriter(IFlvWriterTargetProvider targetProvider) => + public IFlvProcessingContextWriter CreateWriter(IFlvWriterTargetProvider targetProvider, uint maxKeyframeCount) => new FlvProcessingContextWriter( tagWriter: new FlvTagFileWriter(targetProvider: targetProvider, memoryStreamProvider: this.serviceProvider.GetRequiredService(), logger: this.serviceProvider.GetService()), allowMissingHeader: false, - disableKeyframes: false); + maxKeyframeCount: maxKeyframeCount); } } diff --git a/BililiveRecorder.Core/Recording/IFlvProcessingContextWriterFactory.cs b/BililiveRecorder.Core/Recording/IFlvProcessingContextWriterFactory.cs index c42deb5..188b9e8 100644 --- a/BililiveRecorder.Core/Recording/IFlvProcessingContextWriterFactory.cs +++ b/BililiveRecorder.Core/Recording/IFlvProcessingContextWriterFactory.cs @@ -4,6 +4,6 @@ namespace BililiveRecorder.Core.Recording { public interface IFlvProcessingContextWriterFactory { - IFlvProcessingContextWriter CreateWriter(IFlvWriterTargetProvider targetProvider); + IFlvProcessingContextWriter CreateWriter(IFlvWriterTargetProvider targetProvider, uint maxKeyframeCount); } } diff --git a/BililiveRecorder.Core/Recording/StandardRecordTask.cs b/BililiveRecorder.Core/Recording/StandardRecordTask.cs index e6bae24..1e62632 100644 --- a/BililiveRecorder.Core/Recording/StandardRecordTask.cs +++ b/BililiveRecorder.Core/Recording/StandardRecordTask.cs @@ -86,7 +86,7 @@ namespace BililiveRecorder.Core.Recording this.reader = this.tagGroupReaderFactory.CreateTagGroupReader(this.flvTagReaderFactory.CreateFlvTagReader(pipe.Reader)); - this.writer = this.writerFactory.CreateWriter(this.targetProvider); + this.writer = this.writerFactory.CreateWriter(this.targetProvider, this.room.RoomConfig.FlvMetadataKeyframeIndexCount); this.writer.BeforeScriptTagWrite = this.Writer_BeforeScriptTagWrite; this.writer.FileClosed += (sender, e) => { diff --git a/BililiveRecorder.Flv/Writer/FlvProcessingContextWriter.cs b/BililiveRecorder.Flv/Writer/FlvProcessingContextWriter.cs index 4d55044..3e46132 100644 --- a/BililiveRecorder.Flv/Writer/FlvProcessingContextWriter.cs +++ b/BililiveRecorder.Flv/Writer/FlvProcessingContextWriter.cs @@ -12,7 +12,7 @@ namespace BililiveRecorder.Flv.Writer private readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); private readonly IFlvTagWriter tagWriter; private readonly bool allowMissingHeader; - private readonly bool disableKeyframes; + private readonly uint maxKeyframeCount; private bool disposedValue; private WriterState state = WriterState.EmptyFileOrNotOpen; @@ -30,11 +30,11 @@ namespace BililiveRecorder.Flv.Writer public Action? BeforeScriptTagWrite { get; set; } public Action? BeforeScriptTagRewrite { get; set; } - public FlvProcessingContextWriter(IFlvTagWriter tagWriter, bool allowMissingHeader, bool disableKeyframes) + public FlvProcessingContextWriter(IFlvTagWriter tagWriter, bool allowMissingHeader, uint maxKeyframeCount) { this.tagWriter = tagWriter ?? throw new ArgumentNullException(nameof(tagWriter)); this.allowMissingHeader = allowMissingHeader; - this.disableKeyframes = disableKeyframes; + this.maxKeyframeCount = maxKeyframeCount; } public async Task WriteAsync(FlvProcessingContext context) @@ -192,9 +192,9 @@ namespace BililiveRecorder.Flv.Writer { value["duration"] = (ScriptDataNumber)0; - if (!this.disableKeyframes) + if (this.maxKeyframeCount > 0) { - var kfv = new KeyframesScriptDataValue(); + var kfv = new KeyframesScriptDataValue(this.maxKeyframeCount); value["keyframes"] = kfv; this.keyframesScriptDataValue = kfv; } diff --git a/BililiveRecorder.Flv/Writer/KeyframesScriptDataValue.cs b/BililiveRecorder.Flv/Writer/KeyframesScriptDataValue.cs index 9227855..bde056c 100644 --- a/BililiveRecorder.Flv/Writer/KeyframesScriptDataValue.cs +++ b/BililiveRecorder.Flv/Writer/KeyframesScriptDataValue.cs @@ -14,14 +14,15 @@ namespace BililiveRecorder.Flv.Writer internal class KeyframesScriptDataValue : IScriptDataValue { /* + * 以默认 6300 组数据计算 * 最少能保存大约 6300 * 2 second = 3.5 hour 的关键帧索引 * 如果以 5 秒计算则 6300 * 5 second = 8.75 hour + * + * 每组数据的大小为 18 bytes + * 6300 * 18 B ~= 49.2 KiB */ - private const int MaxDataCount = 6300; private const double MinInterval = 1900; - - private const string Keyframes = "keyframes"; private const string Times = "times"; private const string FilePositions = "filepositions"; private const string Spacer = "spacer"; @@ -34,15 +35,24 @@ namespace BililiveRecorder.Flv.Writer public ScriptDataType Type => ScriptDataType.Object; + private readonly uint maxKeyframeCount; private readonly List KeyframesData = new(); + public KeyframesScriptDataValue(uint maxKeyframeCount) + { + this.maxKeyframeCount = maxKeyframeCount; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddData(double time_in_ms, double filePosition) { var keyframesData = this.KeyframesData; - if (keyframesData.Count < MaxDataCount && (keyframesData.Count == 0 || ((time_in_ms - keyframesData[keyframesData.Count - 1].Time) > MinInterval))) + if (keyframesData.Count < this.maxKeyframeCount) { - keyframesData.Add(new Data(time: time_in_ms / 1000d, filePosition: filePosition)); + if (keyframesData.Count == 0 || ((time_in_ms - keyframesData[keyframesData.Count - 1].Time) > MinInterval)) + { + keyframesData.Add(new Data(time: time_in_ms / 1000d, filePosition: filePosition)); + } } } @@ -96,7 +106,7 @@ namespace BililiveRecorder.Flv.Writer WriteKey(stream, SpacerBytes); // array - var count = 2u * (uint)(MaxDataCount - keyframesData.Count); + var count = 2u * (uint)(this.maxKeyframeCount - keyframesData.Count); WriteStrictArray(stream, count); // value diff --git a/BililiveRecorder.ToolBox/Tool/Analyze/AnalyzeHandler.cs b/BililiveRecorder.ToolBox/Tool/Analyze/AnalyzeHandler.cs index a80d8b6..6865fb4 100644 --- a/BililiveRecorder.ToolBox/Tool/Analyze/AnalyzeHandler.cs +++ b/BililiveRecorder.ToolBox/Tool/Analyze/AnalyzeHandler.cs @@ -81,7 +81,7 @@ namespace BililiveRecorder.ToolBox.Tool.Analyze // Pipeline using var grouping = new TagGroupReader(tagReader); - using var writer = new FlvProcessingContextWriter(tagWriter: tagWriter, allowMissingHeader: true, disableKeyframes: true); + using var writer = new FlvProcessingContextWriter(tagWriter: tagWriter, allowMissingHeader: true, maxKeyframeCount: 0); var statsRule = new StatsRule(); var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).Add(statsRule).AddDefault().AddRemoveFillerData().Build(); diff --git a/BililiveRecorder.ToolBox/Tool/Fix/FixHandler.cs b/BililiveRecorder.ToolBox/Tool/Fix/FixHandler.cs index 35194f4..3b365ab 100644 --- a/BililiveRecorder.ToolBox/Tool/Fix/FixHandler.cs +++ b/BililiveRecorder.ToolBox/Tool/Fix/FixHandler.cs @@ -96,7 +96,7 @@ namespace BililiveRecorder.ToolBox.Tool.Fix // Pipeline using var grouping = new TagGroupReader(tagReader); - using var writer = new FlvProcessingContextWriter(tagWriter: tagWriter, allowMissingHeader: true, disableKeyframes: false); + using var writer = new FlvProcessingContextWriter(tagWriter: tagWriter, allowMissingHeader: true, maxKeyframeCount: 0); var statsRule = new StatsRule(); var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).Add(statsRule).AddDefault().AddRemoveFillerData().Build(); diff --git a/BililiveRecorder.WPF/Pages/AdvancedSettingsPage.xaml b/BililiveRecorder.WPF/Pages/AdvancedSettingsPage.xaml index 6e6fdde..381b1c6 100644 --- a/BililiveRecorder.WPF/Pages/AdvancedSettingsPage.xaml +++ b/BililiveRecorder.WPF/Pages/AdvancedSettingsPage.xaml @@ -48,6 +48,14 @@ + + + + + + + + diff --git a/configV2.schema.json b/configV2.schema.json index 3d61c5c..1b7adec 100644 --- a/configV2.schema.json +++ b/configV2.schema.json @@ -229,6 +229,24 @@ } } }, + "FlvMetadataKeyframeIndexCount": { + "description": "FLV文件关键帧索引数量\n默认: 6300", + "markdownDescription": "FLV文件关键帧索引数量 \n默认: `6300 `\n\n索引最少 2 秒一个,间隔小于 2 秒的关键帧会被忽略。关键帧间隔是由主播在直播软件里设置的,通常为 1 到 10 秒。 \n默认 6300 组最少能保存 6300 * 2 second = 3.5 hour 的索引。 \n如果按关键帧间隔 5 秒计算则为 8.75 hour\n\n一组关键帧索引的大小是 18 byte,默认 6300 组所占空间大约为 50 KiB。", + "type": "object", + "additionalProperties": false, + "properties": { + "HasValue": { + "type": "boolean", + "default": true + }, + "Value": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295, + "default": 6300 + } + } + }, "RecordMode": { "description": "录制模式\n默认: RecordMode.Standard", "markdownDescription": "录制模式 \n默认: `RecordMode.Standard `\n\n本设置项是一个 enum,键值对应如下:\n\n| 键 | 值 |\n|:--:|:--:|\n| RecordMode.Standard | 0 |\n| RecordMode.RawData | 1 |\n\n关于录制模式的说明见 [录制模式](/docs/basic/record_mode/)", diff --git a/config_gen/data.ts b/config_gen/data.ts index be4554c..2f96858 100644 --- a/config_gen/data.ts +++ b/config_gen/data.ts @@ -215,4 +215,14 @@ export const data: Array = [ xmlComment: "触发 的弹幕个数", markdown: "" }, + { + name: "FlvMetadataKeyframeIndexCount", + description: "FLV文件关键帧索引数量", + type: "uint", + configType: "globalOnly", + advancedConfig: true, + defaultValue: "6300", + xmlComment: "FLV文件关键帧索引数量", + markdown: "索引最少 2 秒一个,间隔小于 2 秒的关键帧会被忽略。关键帧间隔是由主播在直播软件里设置的,通常为 1 到 10 秒。 \n默认 6300 组最少能保存 6300 * 2 second = 3.5 hour 的索引。 \n如果按关键帧间隔 5 秒计算则为 8.75 hour\n\n一组关键帧索引的大小是 18 byte,默认 6300 组所占空间大约为 50 KiB。" + }, ]; diff --git a/test/BililiveRecorder.Flv.Tests/RuleTests/IntegratedTestBase.cs b/test/BililiveRecorder.Flv.Tests/RuleTests/IntegratedTestBase.cs index fe585c3..d788477 100644 --- a/test/BililiveRecorder.Flv.Tests/RuleTests/IntegratedTestBase.cs +++ b/test/BililiveRecorder.Flv.Tests/RuleTests/IntegratedTestBase.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading.Tasks; using BililiveRecorder.Flv.Grouping; @@ -13,11 +12,9 @@ namespace BililiveRecorder.Flv.Tests.RuleTests { public abstract class IntegratedTestBase { - - protected static async Task RunPipeline(ITagGroupReader reader, IFlvTagWriter output, List comments) { - var writer = new FlvProcessingContextWriter(tagWriter: output, allowMissingHeader: true, disableKeyframes: true); + var writer = new FlvProcessingContextWriter(tagWriter: output, allowMissingHeader: true, maxKeyframeCount: 0); var session = new Dictionary(); var context = new FlvProcessingContext(); var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).AddDefault().AddRemoveFillerData().Build();