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, TimingStreamConnect,
TimingDanmakuRetry, TimingDanmakuRetry,
TimingWatchdogTimeout, TimingWatchdogTimeout,
RecordDanmakuFlushInterval RecordDanmakuFlushInterval,
FlvMetadataKeyframeIndexCount
} }
public enum RoomConfigProperties 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.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.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.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.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 }); 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> /// </summary>
public uint RecordDanmakuFlushInterval => this.GetPropertyValue<uint>(); public uint RecordDanmakuFlushInterval => this.GetPropertyValue<uint>();
/// <summary>
/// FLV文件关键帧索引数量
/// </summary>
public uint FlvMetadataKeyframeIndexCount => this.GetPropertyValue<uint>();
} }
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
@ -347,6 +352,14 @@ namespace BililiveRecorder.Core.Config.V2
[JsonProperty(nameof(RecordDanmakuFlushInterval)), EditorBrowsable(EditorBrowsableState.Never)] [JsonProperty(nameof(RecordDanmakuFlushInterval)), EditorBrowsable(EditorBrowsableState.Never)]
public Optional<uint> OptionalRecordDanmakuFlushInterval { get => this.GetPropertyValueOptional<uint>(nameof(this.RecordDanmakuFlushInterval)); set => this.SetPropertyValueOptional(value, nameof(this.RecordDanmakuFlushInterval)); } 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 public sealed partial class DefaultConfig
@ -398,6 +411,8 @@ namespace BililiveRecorder.Core.Config.V2
public uint RecordDanmakuFlushInterval => 20; 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)); this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
} }
public IFlvProcessingContextWriter CreateWriter(IFlvWriterTargetProvider targetProvider) => public IFlvProcessingContextWriter CreateWriter(IFlvWriterTargetProvider targetProvider, uint maxKeyframeCount) =>
new FlvProcessingContextWriter( new FlvProcessingContextWriter(
tagWriter: new FlvTagFileWriter(targetProvider: targetProvider, tagWriter: new FlvTagFileWriter(targetProvider: targetProvider,
memoryStreamProvider: this.serviceProvider.GetRequiredService<IMemoryStreamProvider>(), memoryStreamProvider: this.serviceProvider.GetRequiredService<IMemoryStreamProvider>(),
logger: this.serviceProvider.GetService<ILogger>()), logger: this.serviceProvider.GetService<ILogger>()),
allowMissingHeader: false, allowMissingHeader: false,
disableKeyframes: false); maxKeyframeCount: maxKeyframeCount);
} }
} }

View File

@ -4,6 +4,6 @@ namespace BililiveRecorder.Core.Recording
{ {
public interface IFlvProcessingContextWriterFactory 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.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.BeforeScriptTagWrite = this.Writer_BeforeScriptTagWrite;
this.writer.FileClosed += (sender, e) => 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 SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
private readonly IFlvTagWriter tagWriter; private readonly IFlvTagWriter tagWriter;
private readonly bool allowMissingHeader; private readonly bool allowMissingHeader;
private readonly bool disableKeyframes; private readonly uint maxKeyframeCount;
private bool disposedValue; private bool disposedValue;
private WriterState state = WriterState.EmptyFileOrNotOpen; private WriterState state = WriterState.EmptyFileOrNotOpen;
@ -30,11 +30,11 @@ namespace BililiveRecorder.Flv.Writer
public Action<ScriptTagBody>? BeforeScriptTagWrite { get; set; } public Action<ScriptTagBody>? BeforeScriptTagWrite { get; set; }
public Action<ScriptTagBody>? BeforeScriptTagRewrite { 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.tagWriter = tagWriter ?? throw new ArgumentNullException(nameof(tagWriter));
this.allowMissingHeader = allowMissingHeader; this.allowMissingHeader = allowMissingHeader;
this.disableKeyframes = disableKeyframes; this.maxKeyframeCount = maxKeyframeCount;
} }
public async Task WriteAsync(FlvProcessingContext context) public async Task WriteAsync(FlvProcessingContext context)
@ -192,9 +192,9 @@ namespace BililiveRecorder.Flv.Writer
{ {
value["duration"] = (ScriptDataNumber)0; value["duration"] = (ScriptDataNumber)0;
if (!this.disableKeyframes) if (this.maxKeyframeCount > 0)
{ {
var kfv = new KeyframesScriptDataValue(); var kfv = new KeyframesScriptDataValue(this.maxKeyframeCount);
value["keyframes"] = kfv; value["keyframes"] = kfv;
this.keyframesScriptDataValue = kfv; this.keyframesScriptDataValue = kfv;
} }

View File

@ -14,14 +14,15 @@ namespace BililiveRecorder.Flv.Writer
internal class KeyframesScriptDataValue : IScriptDataValue internal class KeyframesScriptDataValue : IScriptDataValue
{ {
/* /*
* 6300
* 6300 * 2 second = 3.5 hour * 6300 * 2 second = 3.5 hour
* 5 6300 * 5 second = 8.75 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 double MinInterval = 1900;
private const string Keyframes = "keyframes";
private const string Times = "times"; private const string Times = "times";
private const string FilePositions = "filepositions"; private const string FilePositions = "filepositions";
private const string Spacer = "spacer"; private const string Spacer = "spacer";
@ -34,15 +35,24 @@ namespace BililiveRecorder.Flv.Writer
public ScriptDataType Type => ScriptDataType.Object; public ScriptDataType Type => ScriptDataType.Object;
private readonly uint maxKeyframeCount;
private readonly List<Data> KeyframesData = new(); private readonly List<Data> KeyframesData = new();
public KeyframesScriptDataValue(uint maxKeyframeCount)
{
this.maxKeyframeCount = maxKeyframeCount;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddData(double time_in_ms, double filePosition) public void AddData(double time_in_ms, double filePosition)
{ {
var keyframesData = this.KeyframesData; 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); WriteKey(stream, SpacerBytes);
// array // array
var count = 2u * (uint)(MaxDataCount - keyframesData.Count); var count = 2u * (uint)(this.maxKeyframeCount - keyframesData.Count);
WriteStrictArray(stream, count); WriteStrictArray(stream, count);
// value // value

View File

@ -81,7 +81,7 @@ namespace BililiveRecorder.ToolBox.Tool.Analyze
// Pipeline // Pipeline
using var grouping = new TagGroupReader(tagReader); 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 statsRule = new StatsRule();
var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).Add(statsRule).AddDefault().AddRemoveFillerData().Build(); var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).Add(statsRule).AddDefault().AddRemoveFillerData().Build();

View File

@ -96,7 +96,7 @@ namespace BililiveRecorder.ToolBox.Tool.Fix
// Pipeline // Pipeline
using var grouping = new TagGroupReader(tagReader); 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 statsRule = new StatsRule();
var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).Add(statsRule).AddDefault().AddRemoveFillerData().Build(); 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}"/> <ui:NumberBox Minimum="0" SmallChange="1" Text="{Binding RecordDanmakuFlushInterval,UpdateSourceTrigger=PropertyChanged}"/>
</c:SettingWithDefault> </c:SettingWithDefault>
</GroupBox> </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"> <GroupBox Header="Timing">
<ui:SimpleStackPanel Spacing="10"> <ui:SimpleStackPanel Spacing="10">
<c:SettingWithDefault IsSettingNotUsingDefault="{Binding HasTimingStreamRetry}" Header="录制重试间隔"> <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": { "RecordMode": {
"description": "录制模式\n默认: RecordMode.Standard", "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/)", "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\"/> 的弹幕个数", xmlComment: "触发 <see cref=\"System.Xml.XmlWriter.Flush\"/> 的弹幕个数",
markdown: "" 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.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BililiveRecorder.Flv.Grouping; using BililiveRecorder.Flv.Grouping;
@ -13,11 +12,9 @@ namespace BililiveRecorder.Flv.Tests.RuleTests
{ {
public abstract class IntegratedTestBase public abstract class IntegratedTestBase
{ {
protected static async Task RunPipeline(ITagGroupReader reader, IFlvTagWriter output, List<ProcessingComment> comments) 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 session = new Dictionary<object, object?>();
var context = new FlvProcessingContext(); var context = new FlvProcessingContext();
var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).AddDefault().AddRemoveFillerData().Build(); var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).AddDefault().AddRemoveFillerData().Build();