Add setting for amount of flv keyframe indexes

This commit is contained in:
genteure 2022-01-13 14:28:14 +08:00
parent 44977e688e
commit df1d82c872
13 changed files with 82 additions and 22 deletions

View File

@ -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<GlobalConfig, uint>(config => config.HasTimingDanmakuRetry = false, (config, value) => config.TimingDanmakuRetry = value) { Name = "TimingDanmakuRetry", CanBeOptional = true });
GlobalConfig.Add(GlobalConfigProperties.TimingWatchdogTimeout, new ConfigInstruction<GlobalConfig, uint>(config => config.HasTimingWatchdogTimeout = false, (config, value) => config.TimingWatchdogTimeout = value) { Name = "TimingWatchdogTimeout", CanBeOptional = true });
GlobalConfig.Add(GlobalConfigProperties.RecordDanmakuFlushInterval, new ConfigInstruction<GlobalConfig, uint>(config => config.HasRecordDanmakuFlushInterval = false, (config, value) => config.RecordDanmakuFlushInterval = value) { Name = "RecordDanmakuFlushInterval", CanBeOptional = true });
GlobalConfig.Add(GlobalConfigProperties.FlvMetadataKeyframeIndexCount, new ConfigInstruction<GlobalConfig, uint>(config => config.HasFlvMetadataKeyframeIndexCount = false, (config, value) => config.FlvMetadataKeyframeIndexCount = value) { Name = "FlvMetadataKeyframeIndexCount", CanBeOptional = true });
RoomConfig.Add(RoomConfigProperties.RoomId, new ConfigInstruction<RoomConfig, int>(config => config.HasRoomId = false, (config, value) => config.RoomId = value) { Name = "RoomId", CanBeOptional = false });
RoomConfig.Add(RoomConfigProperties.AutoRecord, new ConfigInstruction<RoomConfig, bool>(config => config.HasAutoRecord = false, (config, value) => config.AutoRecord = value) { Name = "AutoRecord", CanBeOptional = false });

View File

@ -166,6 +166,11 @@ namespace BililiveRecorder.Core.Config.V2
/// </summary>
public uint RecordDanmakuFlushInterval => this.GetPropertyValue<uint>();
/// <summary>
/// FLV文件关键帧索引数量
/// </summary>
public uint FlvMetadataKeyframeIndexCount => this.GetPropertyValue<uint>();
}
[JsonObject(MemberSerialization.OptIn)]
@ -347,6 +352,14 @@ namespace BililiveRecorder.Core.Config.V2
[JsonProperty(nameof(RecordDanmakuFlushInterval)), EditorBrowsable(EditorBrowsableState.Never)]
public Optional<uint> OptionalRecordDanmakuFlushInterval { get => this.GetPropertyValueOptional<uint>(nameof(this.RecordDanmakuFlushInterval)); set => this.SetPropertyValueOptional(value, nameof(this.RecordDanmakuFlushInterval)); }
/// <summary>
/// FLV文件关键帧索引数量
/// </summary>
public uint FlvMetadataKeyframeIndexCount { get => this.GetPropertyValue<uint>(); set => this.SetPropertyValue(value); }
public bool HasFlvMetadataKeyframeIndexCount { get => this.GetPropertyHasValue(nameof(this.FlvMetadataKeyframeIndexCount)); set => this.SetPropertyHasValue<uint>(value, nameof(this.FlvMetadataKeyframeIndexCount)); }
[JsonProperty(nameof(FlvMetadataKeyframeIndexCount)), EditorBrowsable(EditorBrowsableState.Never)]
public Optional<uint> OptionalFlvMetadataKeyframeIndexCount { get => this.GetPropertyValueOptional<uint>(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;
}
}

View File

@ -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<IMemoryStreamProvider>(),
logger: this.serviceProvider.GetService<ILogger>()),
allowMissingHeader: false,
disableKeyframes: false);
maxKeyframeCount: maxKeyframeCount);
}
}

View File

@ -4,6 +4,6 @@ namespace BililiveRecorder.Core.Recording
{
public interface IFlvProcessingContextWriterFactory
{
IFlvProcessingContextWriter CreateWriter(IFlvWriterTargetProvider targetProvider);
IFlvProcessingContextWriter CreateWriter(IFlvWriterTargetProvider targetProvider, uint maxKeyframeCount);
}
}

View File

@ -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) =>
{

View File

@ -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<ScriptTagBody>? BeforeScriptTagWrite { get; set; }
public Action<ScriptTagBody>? 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;
}

View File

@ -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,17 +35,26 @@ namespace BililiveRecorder.Flv.Writer
public ScriptDataType Type => ScriptDataType.Object;
private readonly uint maxKeyframeCount;
private readonly List<Data> 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)
{
if (keyframesData.Count == 0 || ((time_in_ms - keyframesData[keyframesData.Count - 1].Time) > MinInterval))
{
keyframesData.Add(new Data(time: time_in_ms / 1000d, filePosition: filePosition));
}
}
}
/// <summary>
/// <see cref="ScriptDataObject.WriteTo(Stream)"/>
@ -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

View File

@ -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();

View File

@ -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();

View File

@ -48,6 +48,14 @@
<ui:NumberBox Minimum="0" SmallChange="1" Text="{Binding RecordDanmakuFlushInterval,UpdateSourceTrigger=PropertyChanged}"/>
</c:SettingWithDefault>
</GroupBox>
<GroupBox Header="FLV Keyframe Index">
<StackPanel>
<c:SettingWithDefault IsSettingNotUsingDefault="{Binding HasFlvMetadataKeyframeIndexCount}" Header="索引数量 Index Count">
<ui:NumberBox Minimum="0" SmallChange="1" Text="{Binding FlvMetadataKeyframeIndexCount,UpdateSourceTrigger=PropertyChanged}"/>
</c:SettingWithDefault>
<TextBlock Text="一组索引的大小为 18 byte其他信息见网站文档"/>
</StackPanel>
</GroupBox>
<GroupBox Header="Timing">
<ui:SimpleStackPanel Spacing="10">
<c:SettingWithDefault IsSettingNotUsingDefault="{Binding HasTimingStreamRetry}" Header="录制重试间隔">

View File

@ -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/)",

View File

@ -215,4 +215,14 @@ export const data: Array<ConfigEntry> = [
xmlComment: "触发 <see cref=\"System.Xml.XmlWriter.Flush\"/> 的弹幕个数",
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。"
},
];

View File

@ -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<ProcessingComment> 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<object, object?>();
var context = new FlvProcessingContext();
var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).AddDefault().AddRemoveFillerData().Build();