From d0a5ffe9b46ea8e90a877b586ed9a79d3f3295af Mon Sep 17 00:00:00 2001 From: Genteure Date: Wed, 3 Mar 2021 19:04:37 +0800 Subject: [PATCH] FLV: Add RuleTests --- .../DependencyInjectionExtensions.cs | 2 +- .../FlvProcessingContextWriterFactory.cs | 20 - ...ssingContextWriterWithFileWriterFactory.cs | 23 + BililiveRecorder.Core/Recording/RecordTask.cs | 2 - BililiveRecorder.Flv/IFlvTagWriter.cs | 20 + .../IFlvWriterTargetProvider.cs | 3 - .../Parser/FlvTagPipeReader.cs | 4 + .../IProcessingPipelineBuilderExtensions.cs | 2 +- .../Pipeline/PipelineEndAction.cs | 2 +- .../Pipeline/Rules/HandleEndTagRule.cs | 27 + .../Pipeline/Rules/RemoveEndTagRule.cs | 22 - .../Pipeline/Rules/UpdateTimestampRule.cs | 4 +- BililiveRecorder.Flv/Tag.cs | 3 + .../Writer/FlvProcessingContextWriter.cs | 176 +--- .../Writer/FlvTagFileWriter.cs | 160 +++ .../Pages/ToolboxAutoFixPage.xaml.cs | 4 +- BililiveRecorder.sln | 15 +- .../BililiveRecorder.Core.UnitTests.csproj | 2 +- .../BililiveRecorder.Flv.RuleTests.csproj | 33 + .../FlvTagListWriter.cs | 60 ++ .../Integrated/BadTests.cs | 23 + .../Integrated/GoodTests.cs | 59 ++ .../Integrated/TempTest.cs | 19 + .../Integrated/TestBase.cs | 41 + .../SampleDirectoryTestDataAttribute.cs | 29 + .../SampleFileTestDataAttribute.cs | 29 + .../Standalone/Test.cs | 13 + .../samples/bad/.gitkeep | 1 + .../samples/good-relax/.gitkeep | 1 + .../samples/good-strict/.gitkeep | 1 + .../samples/good-strict/obs-fps10.xml | 295 ++++++ .../samples/good-strict/obs-fps2997.xml | 715 +++++++++++++ .../samples/good-strict/obs-fps30.xml | 473 +++++++++ .../samples/good-strict/obs-fps5994.xml | 612 +++++++++++ .../samples/good-strict/obs-fps60.xml | 952 ++++++++++++++++++ .../BililiveRecorder.Flv.UnitTests.csproj | 2 +- .../Grouping/GroupingTest.cs | 3 +- 37 files changed, 3661 insertions(+), 191 deletions(-) delete mode 100644 BililiveRecorder.Core/Recording/FlvProcessingContextWriterFactory.cs create mode 100644 BililiveRecorder.Core/Recording/FlvProcessingContextWriterWithFileWriterFactory.cs create mode 100644 BililiveRecorder.Flv/IFlvTagWriter.cs create mode 100644 BililiveRecorder.Flv/Pipeline/Rules/HandleEndTagRule.cs delete mode 100644 BililiveRecorder.Flv/Pipeline/Rules/RemoveEndTagRule.cs create mode 100644 BililiveRecorder.Flv/Writer/FlvTagFileWriter.cs create mode 100644 test/BililiveRecorder.Flv.RuleTests/BililiveRecorder.Flv.RuleTests.csproj create mode 100644 test/BililiveRecorder.Flv.RuleTests/FlvTagListWriter.cs create mode 100644 test/BililiveRecorder.Flv.RuleTests/Integrated/BadTests.cs create mode 100644 test/BililiveRecorder.Flv.RuleTests/Integrated/GoodTests.cs create mode 100644 test/BililiveRecorder.Flv.RuleTests/Integrated/TempTest.cs create mode 100644 test/BililiveRecorder.Flv.RuleTests/Integrated/TestBase.cs create mode 100644 test/BililiveRecorder.Flv.RuleTests/SampleDirectoryTestDataAttribute.cs create mode 100644 test/BililiveRecorder.Flv.RuleTests/SampleFileTestDataAttribute.cs create mode 100644 test/BililiveRecorder.Flv.RuleTests/Standalone/Test.cs create mode 100644 test/BililiveRecorder.Flv.RuleTests/samples/bad/.gitkeep create mode 100644 test/BililiveRecorder.Flv.RuleTests/samples/good-relax/.gitkeep create mode 100644 test/BililiveRecorder.Flv.RuleTests/samples/good-strict/.gitkeep create mode 100644 test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps10.xml create mode 100644 test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps2997.xml create mode 100644 test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps30.xml create mode 100644 test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps5994.xml create mode 100644 test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps60.xml diff --git a/BililiveRecorder.Core/DependencyInjectionExtensions.cs b/BililiveRecorder.Core/DependencyInjectionExtensions.cs index faf13e6..44d32c6 100644 --- a/BililiveRecorder.Core/DependencyInjectionExtensions.cs +++ b/BililiveRecorder.Core/DependencyInjectionExtensions.cs @@ -45,7 +45,7 @@ namespace BililiveRecorder.DependencyInjection public static IServiceCollection AddRecorderRecording(this IServiceCollection services) => services .AddScoped() - .AddScoped() + .AddScoped() .AddScoped() .AddScoped() ; diff --git a/BililiveRecorder.Core/Recording/FlvProcessingContextWriterFactory.cs b/BililiveRecorder.Core/Recording/FlvProcessingContextWriterFactory.cs deleted file mode 100644 index 0c70c2b..0000000 --- a/BililiveRecorder.Core/Recording/FlvProcessingContextWriterFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using BililiveRecorder.Flv; -using BililiveRecorder.Flv.Writer; -using Microsoft.Extensions.DependencyInjection; - -namespace BililiveRecorder.Core.Recording -{ - public class FlvProcessingContextWriterFactory : IFlvProcessingContextWriterFactory - { - private readonly IServiceProvider serviceProvider; - - public FlvProcessingContextWriterFactory(IServiceProvider serviceProvider) - { - this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - } - - public IFlvProcessingContextWriter CreateWriter(IFlvWriterTargetProvider targetProvider) => - new FlvProcessingContextWriter(targetProvider, this.serviceProvider.GetRequiredService(), null); - } -} diff --git a/BililiveRecorder.Core/Recording/FlvProcessingContextWriterWithFileWriterFactory.cs b/BililiveRecorder.Core/Recording/FlvProcessingContextWriterWithFileWriterFactory.cs new file mode 100644 index 0000000..cf2f7b7 --- /dev/null +++ b/BililiveRecorder.Core/Recording/FlvProcessingContextWriterWithFileWriterFactory.cs @@ -0,0 +1,23 @@ +using System; +using BililiveRecorder.Flv; +using BililiveRecorder.Flv.Writer; +using Microsoft.Extensions.DependencyInjection; +using Serilog; + +namespace BililiveRecorder.Core.Recording +{ + public class FlvProcessingContextWriterWithFileWriterFactory : IFlvProcessingContextWriterFactory + { + private readonly IServiceProvider serviceProvider; + + public FlvProcessingContextWriterWithFileWriterFactory(IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + } + + public IFlvProcessingContextWriter CreateWriter(IFlvWriterTargetProvider targetProvider) => + new FlvProcessingContextWriter(new FlvTagFileWriter(targetProvider, + this.serviceProvider.GetRequiredService(), + this.serviceProvider.GetService())); + } +} diff --git a/BililiveRecorder.Core/Recording/RecordTask.cs b/BililiveRecorder.Core/Recording/RecordTask.cs index f92ca23..646108e 100644 --- a/BililiveRecorder.Core/Recording/RecordTask.cs +++ b/BililiveRecorder.Core/Recording/RecordTask.cs @@ -416,8 +416,6 @@ namespace BililiveRecorder.Core.Recording this.OnNewFile = onNewFile ?? throw new ArgumentNullException(nameof(onNewFile)); } - public bool ShouldCreateNewFile(Stream outputStream, IList tags) => false; - public (Stream stream, object state) CreateOutputStream() { var paths = this.FormatFilename(this.room.RoomConfig.RecordFilenameFormat!); diff --git a/BililiveRecorder.Flv/IFlvTagWriter.cs b/BililiveRecorder.Flv/IFlvTagWriter.cs new file mode 100644 index 0000000..b73a534 --- /dev/null +++ b/BililiveRecorder.Flv/IFlvTagWriter.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using BililiveRecorder.Flv.Amf; + +namespace BililiveRecorder.Flv +{ + public interface IFlvTagWriter : IDisposable + { + long FileSize { get; } + object? State { get; } + + Task CreateNewFile(); + bool CloseCurrentFile(); + Task WriteTag(Tag tag); + Task OverwriteMetadata(ScriptTagBody metadata); + + Task WriteAlternativeHeaders(IEnumerable tags); + } +} diff --git a/BililiveRecorder.Flv/IFlvWriterTargetProvider.cs b/BililiveRecorder.Flv/IFlvWriterTargetProvider.cs index 3a24ce4..b8b34f1 100644 --- a/BililiveRecorder.Flv/IFlvWriterTargetProvider.cs +++ b/BililiveRecorder.Flv/IFlvWriterTargetProvider.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.IO; namespace BililiveRecorder.Flv @@ -8,7 +7,5 @@ namespace BililiveRecorder.Flv (Stream stream, object state) CreateOutputStream(); Stream CreateAlternativeHeaderStream(); - - bool ShouldCreateNewFile(Stream outputStream, IList tags); } } diff --git a/BililiveRecorder.Flv/Parser/FlvTagPipeReader.cs b/BililiveRecorder.Flv/Parser/FlvTagPipeReader.cs index 283f2f6..506cbd2 100644 --- a/BililiveRecorder.Flv/Parser/FlvTagPipeReader.cs +++ b/BililiveRecorder.Flv/Parser/FlvTagPipeReader.cs @@ -17,11 +17,14 @@ namespace BililiveRecorder.Flv.Parser public class FlvTagPipeReader : IFlvTagReader, IDisposable { private static int memoryCreateCounter = 0; + private readonly ILogger? logger; private readonly IMemoryStreamProvider memoryStreamProvider; private readonly bool skipData; private readonly bool leaveOpen; + private int tagIndex; + private bool peek = false; private Tag? peekTag = null; private readonly SemaphoreSlim peekSemaphoreSlim = new SemaphoreSlim(1, 1); @@ -263,6 +266,7 @@ namespace BililiveRecorder.Flv.Parser { Type = tagType, Flag = tagFlag, + Index = Interlocked.Increment(ref this.tagIndex), Size = tagSize, Timestamp = tagTimestamp, }; diff --git a/BililiveRecorder.Flv/Pipeline/IProcessingPipelineBuilderExtensions.cs b/BililiveRecorder.Flv/Pipeline/IProcessingPipelineBuilderExtensions.cs index e6fea28..7ac1969 100644 --- a/BililiveRecorder.Flv/Pipeline/IProcessingPipelineBuilderExtensions.cs +++ b/BililiveRecorder.Flv/Pipeline/IProcessingPipelineBuilderExtensions.cs @@ -24,7 +24,7 @@ namespace BililiveRecorder.Flv.Pipeline public static IProcessingPipelineBuilder AddDefault(this IProcessingPipelineBuilder builder) => builder - .Add() + .Add() .Add() .Add() .Add() diff --git a/BililiveRecorder.Flv/Pipeline/PipelineEndAction.cs b/BililiveRecorder.Flv/Pipeline/PipelineEndAction.cs index 395c9ab..6eccd5c 100644 --- a/BililiveRecorder.Flv/Pipeline/PipelineEndAction.cs +++ b/BililiveRecorder.Flv/Pipeline/PipelineEndAction.cs @@ -2,7 +2,7 @@ namespace BililiveRecorder.Flv.Pipeline { public class PipelineEndAction : PipelineAction { - private Tag Tag { get; set; } + public Tag Tag { get; set; } public PipelineEndAction(Tag tag) { diff --git a/BililiveRecorder.Flv/Pipeline/Rules/HandleEndTagRule.cs b/BililiveRecorder.Flv/Pipeline/Rules/HandleEndTagRule.cs new file mode 100644 index 0000000..bcd1089 --- /dev/null +++ b/BililiveRecorder.Flv/Pipeline/Rules/HandleEndTagRule.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading.Tasks; + +namespace BililiveRecorder.Flv.Pipeline.Rules +{ + /// + /// 处理 end tag + /// + public class HandleEndTagRule : ISimpleProcessingRule + { + public Task RunAsync(FlvProcessingContext context, Func next) + { + if (context.OriginalInput is PipelineEndAction end) + { + if (context.SessionItems.TryGetValue(UpdateTimestampRule.TS_STORE_KEY, out var obj) && obj is UpdateTimestampRule.TimestampStore store) + end.Tag.Timestamp -= store.CurrentOffset; + else + end.Tag.Timestamp = 0; + + context.AddNewFileAtEnd(); + return Task.CompletedTask; + } + else + return next(); + } + } +} diff --git a/BililiveRecorder.Flv/Pipeline/Rules/RemoveEndTagRule.cs b/BililiveRecorder.Flv/Pipeline/Rules/RemoveEndTagRule.cs deleted file mode 100644 index c0d42ec..0000000 --- a/BililiveRecorder.Flv/Pipeline/Rules/RemoveEndTagRule.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace BililiveRecorder.Flv.Pipeline.Rules -{ - /// - /// 移除 end tag - /// - public class RemoveEndTagRule : ISimpleProcessingRule - { - public Task RunAsync(FlvProcessingContext context, Func next) - { - if (context.OriginalInput is PipelineEndAction) - { - context.ClearOutput(); - return Task.CompletedTask; - } - else - return next(); - } - } -} diff --git a/BililiveRecorder.Flv/Pipeline/Rules/UpdateTimestampRule.cs b/BililiveRecorder.Flv/Pipeline/Rules/UpdateTimestampRule.cs index 180ba77..0dcb53d 100644 --- a/BililiveRecorder.Flv/Pipeline/Rules/UpdateTimestampRule.cs +++ b/BililiveRecorder.Flv/Pipeline/Rules/UpdateTimestampRule.cs @@ -7,7 +7,7 @@ namespace BililiveRecorder.Flv.Pipeline.Rules { public class UpdateTimestampRule : ISimpleProcessingRule { - private const string TS_STORE_KEY = "Timestamp_Store_Key"; + public const string TS_STORE_KEY = "Timestamp_Store_Key"; private const int JUMP_THRESHOLD = 50; @@ -112,7 +112,7 @@ namespace BililiveRecorder.Flv.Pipeline.Rules return sample[0].Timestamp - lastTimestamp - duration; } - private class TimestampStore + public class TimestampStore { public int LastOriginal; diff --git a/BililiveRecorder.Flv/Tag.cs b/BililiveRecorder.Flv/Tag.cs index eb9e202..0ea385f 100644 --- a/BililiveRecorder.Flv/Tag.cs +++ b/BililiveRecorder.Flv/Tag.cs @@ -16,6 +16,9 @@ namespace BililiveRecorder.Flv [XmlAttribute] public TagFlag Flag { get; set; } + [XmlIgnore] + public long Index { get; set; } = -1; + [XmlAttribute] public uint Size { get; set; } diff --git a/BililiveRecorder.Flv/Writer/FlvProcessingContextWriter.cs b/BililiveRecorder.Flv/Writer/FlvProcessingContextWriter.cs index 22d7d15..29e92eb 100644 --- a/BililiveRecorder.Flv/Writer/FlvProcessingContextWriter.cs +++ b/BililiveRecorder.Flv/Writer/FlvProcessingContextWriter.cs @@ -1,37 +1,24 @@ using System; -using System.Buffers; -using System.Buffers.Binary; -using System.Diagnostics; -using System.IO; -using System.Text; using System.Threading; using System.Threading.Tasks; using BililiveRecorder.Flv.Amf; using BililiveRecorder.Flv.Pipeline; -using Serilog; namespace BililiveRecorder.Flv.Writer { public class FlvProcessingContextWriter : IFlvProcessingContextWriter, IDisposable { - private static readonly byte[] FLV_FILE_HEADER = new byte[] { (byte)'F', (byte)'L', (byte)'V', 1, 0b0000_0101, 0, 0, 0, 9, 0, 0, 0, 0 }; - private readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); - private readonly IFlvWriterTargetProvider targetProvider; - private readonly IMemoryStreamProvider memoryStreamProvider; - private readonly ILogger? logger; + private readonly IFlvTagWriter tagWriter; private bool disposedValue; - private WriterState state = WriterState.EmptyFileOrNotOpen; - private Stream? stream = null; - private object? streamState = null; + private WriterState state = WriterState.EmptyFileOrNotOpen; private Tag? nextScriptTag = null; private Tag? nextAudioHeaderTag = null; private Tag? nextVideoHeaderTag = null; private ScriptTagBody? lastScriptBody = null; - private uint lastScriptBodyLength = 0; private double lastDuration; public event EventHandler? FileClosed; @@ -39,11 +26,9 @@ namespace BililiveRecorder.Flv.Writer public Action? BeforeScriptTagWrite { get; set; } public Action? BeforeScriptTagRewrite { get; set; } - public FlvProcessingContextWriter(IFlvWriterTargetProvider targetProvider, IMemoryStreamProvider memoryStreamProvider, ILogger? logger) + public FlvProcessingContextWriter(IFlvTagWriter tagWriter) { - this.targetProvider = targetProvider ?? throw new ArgumentNullException(nameof(targetProvider)); - this.memoryStreamProvider = memoryStreamProvider ?? throw new ArgumentNullException(nameof(memoryStreamProvider)); - this.logger = logger?.ForContext(); + this.tagWriter = tagWriter ?? throw new ArgumentNullException(nameof(tagWriter)); } public async Task WriteAsync(FlvProcessingContext context) @@ -94,26 +79,13 @@ namespace BililiveRecorder.Flv.Writer PipelineScriptAction scriptAction => this.WriteScriptTag(scriptAction), PipelineHeaderAction headerAction => this.WriteHeaderTags(headerAction), PipelineDataAction dataAction => this.WriteDataTags(dataAction), + PipelineEndAction endAction => this.WriteEndTag(endAction), PipelineLogAlternativeHeaderAction logAlternativeHeaderAction => this.WriteAlternativeHeader(logAlternativeHeaderAction), _ => Task.CompletedTask, }; - private async Task WriteAlternativeHeader(PipelineLogAlternativeHeaderAction logAlternativeHeaderAction) - { - using var writer = new StreamWriter(this.targetProvider.CreateAlternativeHeaderStream(), Encoding.UTF8); - await writer.WriteLineAsync("----- Group Start -----").ConfigureAwait(false); - await writer.WriteLineAsync("连续遇到了多个不同的音视频Header,如果录制的文件不能正常播放可以尝试用这里的数据进行修复").ConfigureAwait(false); - await writer.WriteLineAsync(DateTimeOffset.Now.ToString("O")).ConfigureAwait(false); - - foreach (var tag in logAlternativeHeaderAction.Tags) - { - await writer.WriteLineAsync().ConfigureAwait(false); - await writer.WriteLineAsync(tag.ToString()).ConfigureAwait(false); - await writer.WriteLineAsync(tag.BinaryDataForSerializationUseOnly).ConfigureAwait(false); - } - - await writer.WriteLineAsync("----- Group End -----").ConfigureAwait(false); - } + private Task WriteAlternativeHeader(PipelineLogAlternativeHeaderAction logAlternativeHeaderAction) => + this.tagWriter.WriteAlternativeHeaders(logAlternativeHeaderAction.Tags); private Task OpenNewFile() { @@ -146,70 +118,41 @@ namespace BililiveRecorder.Flv.Writer private void CloseCurrentFileImpl() { - if (this.stream is null) - return; - - //await this.RewriteScriptTagImpl(0).ConfigureAwait(false); - //await this.stream.FlushAsync().ConfigureAwait(false); - var eventArgs = new FileClosedEventArgs { - FileSize = this.stream.Length, + FileSize = this.tagWriter.FileSize, Duration = this.lastDuration, - State = this.streamState, + State = this.tagWriter.State, }; - this.stream.Close(); - this.stream.Dispose(); - this.stream = null; - - this.streamState = null; - this.lastDuration = 0d; - - FileClosed?.Invoke(this, eventArgs); + if (this.tagWriter.CloseCurrentFile()) + { + this.lastDuration = 0d; + FileClosed?.Invoke(this, eventArgs); + } } private async Task OpenNewFileImpl() { this.CloseCurrentFileImpl(); - Debug.Assert(this.stream is null, "stream is not null"); - - (this.stream, this.streamState) = this.targetProvider.CreateOutputStream(); - await this.stream.WriteAsync(FLV_FILE_HEADER, 0, FLV_FILE_HEADER.Length).ConfigureAwait(false); + await this.tagWriter.CreateNewFile().ConfigureAwait(false); this.state = WriterState.BeforeScript; } private async Task RewriteScriptTagImpl(double duration) { - if (this.stream is null || this.lastScriptBody is null) + if (this.lastScriptBody is null) return; var value = this.lastScriptBody.GetMetadataValue(); - if (!(value is null)) + if (value is not null) value["duration"] = (ScriptDataNumber)duration; this.BeforeScriptTagRewrite?.Invoke(this.lastScriptBody); - this.stream.Seek(9 + 4 + 11, SeekOrigin.Begin); - - using (var buf = this.memoryStreamProvider.CreateMemoryStream(nameof(FlvProcessingContextWriter) + ":" + nameof(RewriteScriptTagImpl) + ":Temp")) - { - this.lastScriptBody.WriteTo(buf); - if (buf.Length == this.lastScriptBodyLength) - { - buf.Seek(0, SeekOrigin.Begin); - await buf.CopyToAsync(this.stream); - await this.stream.FlushAsync(); - } - else - { - this.logger?.Warning("因 Script tag 输出长度不一致跳过修改"); - } - } - - this.stream.Seek(0, SeekOrigin.End); + await this.tagWriter.OverwriteMetadata(this.lastScriptBody).ConfigureAwait(false); } private async Task WriteScriptTagImpl() @@ -220,64 +163,29 @@ namespace BililiveRecorder.Flv.Writer if (this.nextScriptTag.ScriptData is null) throw new InvalidOperationException("ScriptData is null"); - if (this.stream is null) - throw new Exception("stream is null"); - this.lastScriptBody = this.nextScriptTag.ScriptData; var value = this.lastScriptBody.GetMetadataValue(); - if (!(value is null)) + if (value is not null) value["duration"] = (ScriptDataNumber)0; this.BeforeScriptTagWrite?.Invoke(this.lastScriptBody); - var bytes = ArrayPool.Shared.Rent(11); - try - { - using var bodyStream = this.memoryStreamProvider.CreateMemoryStream(nameof(FlvProcessingContextWriter) + ":" + nameof(WriteScriptTagImpl) + ":Temp"); - this.lastScriptBody.WriteTo(bodyStream); - this.lastScriptBodyLength = (uint)bodyStream.Length; - bodyStream.Seek(0, SeekOrigin.Begin); + await this.tagWriter.WriteTag(this.nextScriptTag).ConfigureAwait(false); - BinaryPrimitives.WriteUInt32BigEndian(new Span(bytes, 0, 4), this.lastScriptBodyLength); - bytes[0] = (byte)TagType.Script; - - bytes[4] = 0; - bytes[5] = 0; - bytes[6] = 0; - bytes[7] = 0; - bytes[8] = 0; - bytes[9] = 0; - bytes[10] = 0; - - await this.stream.WriteAsync(bytes, 0, 11).ConfigureAwait(false); - await bodyStream.CopyToAsync(this.stream); - - BinaryPrimitives.WriteUInt32BigEndian(new Span(bytes, 0, 4), this.lastScriptBodyLength + 11); - await this.stream.WriteAsync(bytes, 0, 4).ConfigureAwait(false); - - await this.stream.FlushAsync(); - } - finally - { - ArrayPool.Shared.Return(bytes); - } this.state = WriterState.BeforeHeader; } private async Task WriteHeaderTagsImpl() { - if (this.stream is null) - throw new Exception("stream is null"); - if (this.nextVideoHeaderTag is null) throw new InvalidOperationException("No video header tag availible"); if (this.nextAudioHeaderTag is null) throw new InvalidOperationException("No audio header tag availible"); - await this.nextVideoHeaderTag.WriteTo(this.stream, 0, this.memoryStreamProvider).ConfigureAwait(false); - await this.nextAudioHeaderTag.WriteTo(this.stream, 0, this.memoryStreamProvider).ConfigureAwait(false); + await this.tagWriter.WriteTag(this.nextVideoHeaderTag).ConfigureAwait(false); + await this.tagWriter.WriteTag(this.nextAudioHeaderTag).ConfigureAwait(false); this.state = WriterState.Writing; } @@ -299,31 +207,43 @@ namespace BililiveRecorder.Flv.Writer await this.WriteHeaderTagsImpl().ConfigureAwait(false); break; case WriterState.Writing: - if (this.stream is null) - throw new Exception("stream is null"); - - if (this.targetProvider.ShouldCreateNewFile(this.stream, dataAction.Tags)) - { - await this.OpenNewFileImpl().ConfigureAwait(false); - await this.WriteScriptTagImpl().ConfigureAwait(false); - await this.WriteHeaderTagsImpl().ConfigureAwait(false); - } break; default: throw new InvalidOperationException($"Can't write data tag with current state ({this.state})"); } - if (this.stream is null) - throw new Exception("stream is null"); - foreach (var tag in dataAction.Tags) - await tag.WriteTo(this.stream, tag.Timestamp, this.memoryStreamProvider).ConfigureAwait(false); + await this.tagWriter.WriteTag(tag).ConfigureAwait(false); var duration = dataAction.Tags[dataAction.Tags.Count - 1].Timestamp / 1000d; this.lastDuration = duration; await this.RewriteScriptTagImpl(duration).ConfigureAwait(false); } + private async Task WriteEndTag(PipelineEndAction endAction) + { + switch (this.state) + { + case WriterState.EmptyFileOrNotOpen: + await this.OpenNewFileImpl().ConfigureAwait(false); + await this.WriteScriptTagImpl().ConfigureAwait(false); + await this.WriteHeaderTagsImpl().ConfigureAwait(false); + break; + case WriterState.BeforeScript: + await this.WriteScriptTagImpl().ConfigureAwait(false); + await this.WriteHeaderTagsImpl().ConfigureAwait(false); + break; + case WriterState.BeforeHeader: + await this.WriteHeaderTagsImpl().ConfigureAwait(false); + break; + case WriterState.Writing: + break; + default: + throw new InvalidOperationException($"Can't write data tag with current state ({this.state})"); + } + + await this.tagWriter.WriteTag(endAction.Tag).ConfigureAwait(false); + } #endregion #region IDisposable @@ -335,7 +255,7 @@ namespace BililiveRecorder.Flv.Writer if (disposing) { // dispose managed state (managed objects) - this.CloseCurrentFileImpl(); + this.tagWriter.Dispose(); } // free unmanaged resources (unmanaged objects) and override finalizer diff --git a/BililiveRecorder.Flv/Writer/FlvTagFileWriter.cs b/BililiveRecorder.Flv/Writer/FlvTagFileWriter.cs new file mode 100644 index 0000000..59910f3 --- /dev/null +++ b/BililiveRecorder.Flv/Writer/FlvTagFileWriter.cs @@ -0,0 +1,160 @@ +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using BililiveRecorder.Flv.Amf; +using Serilog; + +namespace BililiveRecorder.Flv.Writer +{ + public class FlvTagFileWriter : IFlvTagWriter + { + private static readonly byte[] FLV_FILE_HEADER = new byte[] { (byte)'F', (byte)'L', (byte)'V', 1, 0b0000_0101, 0, 0, 0, 9, 0, 0, 0, 0 }; + + private readonly IFlvWriterTargetProvider targetProvider; + private readonly IMemoryStreamProvider memoryStreamProvider; + private readonly ILogger? logger; + + private Stream? stream; + private uint lastMetadataLength; + + public FlvTagFileWriter(IFlvWriterTargetProvider targetProvider, IMemoryStreamProvider memoryStreamProvider, ILogger? logger) + { + this.targetProvider = targetProvider ?? throw new ArgumentNullException(nameof(targetProvider)); + this.memoryStreamProvider = memoryStreamProvider ?? throw new ArgumentNullException(nameof(memoryStreamProvider)); + this.logger = logger?.ForContext(); + } + + public long FileSize => this.stream?.Length ?? 0; + public object? State { get; private set; } + + public bool CloseCurrentFile() + { + if (this.stream is null) + return false; + + this.stream.Close(); + this.stream.Dispose(); + this.stream = null; + + return true; + } + + public async Task CreateNewFile() + { + this.stream?.Dispose(); + + (this.stream, this.State) = this.targetProvider.CreateOutputStream(); + + await this.stream.WriteAsync(FLV_FILE_HEADER, 0, FLV_FILE_HEADER.Length).ConfigureAwait(false); + } + + public async Task OverwriteMetadata(ScriptTagBody metadata) + { + if (this.stream is null || metadata is null) + return; + + using var buf = this.memoryStreamProvider.CreateMemoryStream(nameof(FlvTagFileWriter) + ":" + nameof(OverwriteMetadata) + ":Temp"); + metadata.WriteTo(buf); + if (buf.Length == this.lastMetadataLength) + { + buf.Seek(0, SeekOrigin.Begin); + try + { + this.stream.Seek(9 + 4 + 11, SeekOrigin.Begin); + await buf.CopyToAsync(this.stream); + await this.stream.FlushAsync(); + } + finally + { + this.stream.Seek(0, SeekOrigin.End); + } + } + else + { + this.logger?.Warning("因 Script tag 输出长度不一致跳过修改"); + } + } + + public async Task WriteAlternativeHeaders(IEnumerable tags) + { + using var writer = new StreamWriter(this.targetProvider.CreateAlternativeHeaderStream(), Encoding.UTF8); + await writer.WriteLineAsync("----- Group Start -----").ConfigureAwait(false); + await writer.WriteLineAsync("连续遇到了多个不同的音视频Header,如果录制的文件不能正常播放可以尝试用这里的数据进行修复").ConfigureAwait(false); + await writer.WriteLineAsync(DateTimeOffset.Now.ToString("O")).ConfigureAwait(false); + + foreach (var tag in tags) + { + await writer.WriteLineAsync().ConfigureAwait(false); + await writer.WriteLineAsync(tag.ToString()).ConfigureAwait(false); + await writer.WriteLineAsync(tag.BinaryDataForSerializationUseOnly).ConfigureAwait(false); + } + + await writer.WriteLineAsync("----- Group End -----").ConfigureAwait(false); + } + + public Task WriteTag(Tag tag) + { + if (this.stream is null) + throw new InvalidOperationException("stream is null"); + + return tag switch + { + Tag { Type: TagType.Script } => this.WriteScriptTagImpl(tag), + Tag header when header.IsHeader() => header.WriteTo(this.stream, 0, this.memoryStreamProvider), + Tag => tag.WriteTo(this.stream, tag.Timestamp, this.memoryStreamProvider), + _ => Task.CompletedTask, + }; + } + + private async Task WriteScriptTagImpl(Tag tag) + { + if (this.stream is null) + throw new Exception("stream is null"); + + if (tag.ScriptData is null) + throw new Exception("Script Data is null"); + + var bytes = ArrayPool.Shared.Rent(11); + try + { + using var bodyStream = this.memoryStreamProvider.CreateMemoryStream(nameof(FlvTagFileWriter) + ":" + nameof(WriteScriptTagImpl) + ":Temp"); + tag.ScriptData.WriteTo(bodyStream); + this.lastMetadataLength = (uint)bodyStream.Length; + bodyStream.Seek(0, SeekOrigin.Begin); + + BinaryPrimitives.WriteUInt32BigEndian(new Span(bytes, 0, 4), this.lastMetadataLength); + bytes[0] = (byte)TagType.Script; + + bytes[4] = 0; + bytes[5] = 0; + bytes[6] = 0; + bytes[7] = 0; + bytes[8] = 0; + bytes[9] = 0; + bytes[10] = 0; + + await this.stream.WriteAsync(bytes, 0, 11).ConfigureAwait(false); + await bodyStream.CopyToAsync(this.stream); + + BinaryPrimitives.WriteUInt32BigEndian(new Span(bytes, 0, 4), this.lastMetadataLength + 11); + await this.stream.WriteAsync(bytes, 0, 4).ConfigureAwait(false); + + await this.stream.FlushAsync(); + } + finally + { + ArrayPool.Shared.Return(bytes); + } + } + + public void Dispose() + { + this.stream?.Dispose(); + this.stream = null; + } + } +} diff --git a/BililiveRecorder.WPF/Pages/ToolboxAutoFixPage.xaml.cs b/BililiveRecorder.WPF/Pages/ToolboxAutoFixPage.xaml.cs index d59c301..41190dd 100644 --- a/BililiveRecorder.WPF/Pages/ToolboxAutoFixPage.xaml.cs +++ b/BililiveRecorder.WPF/Pages/ToolboxAutoFixPage.xaml.cs @@ -95,7 +95,7 @@ namespace BililiveRecorder.WPF.Pages using var inputStream = File.OpenRead(inputPath); var memoryStreamProvider = new DefaultMemoryStreamProvider(); using var grouping = new TagGroupReader(new FlvTagPipeReader(PipeReader.Create(inputStream), memoryStreamProvider, skipData: false, logger: logger)); - using var writer = new FlvProcessingContextWriter(targetProvider, memoryStreamProvider, logger); + using var writer = new FlvProcessingContextWriter(new FlvTagFileWriter(targetProvider, memoryStreamProvider, logger)); var context = new FlvProcessingContext(); var session = new Dictionary(); var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).AddDefault().AddRemoveFillerData().Build(); @@ -345,8 +345,6 @@ namespace BililiveRecorder.WPF.Pages var fileStream = File.Create(path); return (fileStream, null!); } - - public bool ShouldCreateNewFile(Stream outputStream, IList tags) => false; } } } diff --git a/BililiveRecorder.sln b/BililiveRecorder.sln index 916b5bd..d0dac8e 100644 --- a/BililiveRecorder.sln +++ b/BililiveRecorder.sln @@ -27,6 +27,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BililiveRecorder.Core.UnitT EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BililiveRecorder.Flv.UnitTests", "test\BililiveRecorder.Flv.UnitTests\BililiveRecorder.Flv.UnitTests.csproj", "{560E8483-9293-410E-81E9-AB36B49F8A7C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BililiveRecorder.Flv.RuleTests", "test\BililiveRecorder.Flv.RuleTests\BililiveRecorder.Flv.RuleTests.csproj", "{75DA0162-DE06-4FA0-B6F8-C82C11AF65BC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -57,6 +59,10 @@ Global {560E8483-9293-410E-81E9-AB36B49F8A7C}.Debug|Any CPU.Build.0 = Debug|Any CPU {560E8483-9293-410E-81E9-AB36B49F8A7C}.Release|Any CPU.ActiveCfg = Release|Any CPU {560E8483-9293-410E-81E9-AB36B49F8A7C}.Release|Any CPU.Build.0 = Release|Any CPU + {75DA0162-DE06-4FA0-B6F8-C82C11AF65BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75DA0162-DE06-4FA0-B6F8-C82C11AF65BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75DA0162-DE06-4FA0-B6F8-C82C11AF65BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75DA0162-DE06-4FA0-B6F8-C82C11AF65BC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -68,12 +74,13 @@ Global {7610E19C-D3AB-4CBC-983E-6FDA36F4D4B3} = {2D44A59D-E437-4FEE-8A2E-3FF00D53A64D} {521EC763-5694-45A8-B87F-6E6B7F2A3BD4} = {623A2ACC-DAC6-4E6F-9242-B4B54381AAE1} {560E8483-9293-410E-81E9-AB36B49F8A7C} = {623A2ACC-DAC6-4E6F-9242-B4B54381AAE1} + {75DA0162-DE06-4FA0-B6F8-C82C11AF65BC} = {623A2ACC-DAC6-4E6F-9242-B4B54381AAE1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - RESX_SortFileContentOnSave = True - SolutionGuid = {F3CB8B14-077A-458F-BD8E-1747ED0F5170} - RESX_NeutralResourcesLanguage = zh-Hans - RESX_SaveFilesImmediatelyUponChange = True RESX_ShowErrorsInErrorList = False + RESX_SaveFilesImmediatelyUponChange = True + RESX_NeutralResourcesLanguage = zh-Hans + SolutionGuid = {F3CB8B14-077A-458F-BD8E-1747ED0F5170} + RESX_SortFileContentOnSave = True EndGlobalSection EndGlobal diff --git a/test/BililiveRecorder.Core.UnitTests/BililiveRecorder.Core.UnitTests.csproj b/test/BililiveRecorder.Core.UnitTests/BililiveRecorder.Core.UnitTests.csproj index c78087f..f0dc49d 100644 --- a/test/BililiveRecorder.Core.UnitTests/BililiveRecorder.Core.UnitTests.csproj +++ b/test/BililiveRecorder.Core.UnitTests/BililiveRecorder.Core.UnitTests.csproj @@ -8,7 +8,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/BililiveRecorder.Flv.RuleTests/BililiveRecorder.Flv.RuleTests.csproj b/test/BililiveRecorder.Flv.RuleTests/BililiveRecorder.Flv.RuleTests.csproj new file mode 100644 index 0000000..e7e1569 --- /dev/null +++ b/test/BililiveRecorder.Flv.RuleTests/BililiveRecorder.Flv.RuleTests.csproj @@ -0,0 +1,33 @@ + + + + netcoreapp3.1 + + false + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + Always + + + + diff --git a/test/BililiveRecorder.Flv.RuleTests/FlvTagListWriter.cs b/test/BililiveRecorder.Flv.RuleTests/FlvTagListWriter.cs new file mode 100644 index 0000000..a882f14 --- /dev/null +++ b/test/BililiveRecorder.Flv.RuleTests/FlvTagListWriter.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using BililiveRecorder.Flv.Amf; + +namespace BililiveRecorder.Flv.RuleTests +{ + public class FlvTagListWriter : IFlvTagWriter + { + private List? file; + + public FlvTagListWriter() + { + this.Files = new List>(); + this.AlternativeHeaders = new List(); + } + + public List> Files { get; } + public List AlternativeHeaders { get; } + + public long FileSize => -1; + + public object? State => null; + + public bool CloseCurrentFile() + { + if (this.file is null) + return false; + + this.file = null; + return true; + } + + public Task CreateNewFile() + { + this.file = new List(); + this.Files.Add(this.file); + return Task.CompletedTask; + } + + public void Dispose() { } + + public Task OverwriteMetadata(ScriptTagBody metadata) => Task.CompletedTask; + + public Task WriteAlternativeHeaders(IEnumerable tags) + { + this.AlternativeHeaders.AddRange(tags); + return Task.CompletedTask; + } + + public Task WriteTag(Tag tag) + { + if (this.file is null) + throw new InvalidOperationException(); + + this.file.Add(tag); + return Task.CompletedTask; + } + } +} diff --git a/test/BililiveRecorder.Flv.RuleTests/Integrated/BadTests.cs b/test/BililiveRecorder.Flv.RuleTests/Integrated/BadTests.cs new file mode 100644 index 0000000..4581136 --- /dev/null +++ b/test/BililiveRecorder.Flv.RuleTests/Integrated/BadTests.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace BililiveRecorder.Flv.RuleTests.Integrated +{ + public class BadTests : TestBase + { + [Theory(Skip = "no data yet")] + [SampleDirectoryTestData("samples/bad")] + public void Test(string path) + { + var path_input = Path.Combine(path, "input.xml"); + var path_output = Path.Combine(path, "output.xml"); + + + } + } +} diff --git a/test/BililiveRecorder.Flv.RuleTests/Integrated/GoodTests.cs b/test/BililiveRecorder.Flv.RuleTests/Integrated/GoodTests.cs new file mode 100644 index 0000000..ddc716a --- /dev/null +++ b/test/BililiveRecorder.Flv.RuleTests/Integrated/GoodTests.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using BililiveRecorder.Flv.Grouping; +using BililiveRecorder.Flv.Pipeline; +using BililiveRecorder.Flv.Xml; +using Xunit; + +namespace BililiveRecorder.Flv.RuleTests.Integrated +{ + public class GoodTests : TestBase + { + [Theory] + [SampleFileTestData("samples/good-strict")] + public async Task StrictTestsAsync(string path) + { + // Arrange + var original = this.LoadFile(path).Tags; + var reader = new TagGroupReader(new FlvTagListReader(this.LoadFile(path).Tags)); + var output = new FlvTagListWriter(); + var comments = new List(); + + // Act + await this.RunPipeline(reader, output, comments).ConfigureAwait(false); + + // Assert + Assert.Empty(comments); + Assert.Empty(output.AlternativeHeaders); + Assert.Single(output.Files); + Assert.Equal(original.Count, output.Files[0].Count); + + var file = output.Files[0]; + for (var i = 0; i < original.Count; i++) + { + var a = original[i]; + var b = file[i]; + + Assert.Equal(a.Type, b.Type); + Assert.Equal(a.Timestamp, a.Timestamp); + Assert.Equal(a.Flag, b.Flag); + + if (a.IsHeader()) + { + Assert.Equal(a.BinaryDataForSerializationUseOnly, b.BinaryDataForSerializationUseOnly); + } + else if (!a.IsScript()) + { + Assert.Equal(a.Index, b.Index); + } + } + } + + //[Theory(Skip = "no data yet")] + //[SampleFileTestData("samples/good-relax")] + //public void RelaxTests(string path) + //{ + + //} + } +} diff --git a/test/BililiveRecorder.Flv.RuleTests/Integrated/TempTest.cs b/test/BililiveRecorder.Flv.RuleTests/Integrated/TempTest.cs new file mode 100644 index 0000000..9348f6b --- /dev/null +++ b/test/BililiveRecorder.Flv.RuleTests/Integrated/TempTest.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace BililiveRecorder.Flv.RuleTests.Integrated +{ + public class TempTest + { + [Theory] + [SampleDirectoryTestData("samples")] + public void Test(string path) + { + string.IsNullOrWhiteSpace(path); + } + } +} diff --git a/test/BililiveRecorder.Flv.RuleTests/Integrated/TestBase.cs b/test/BililiveRecorder.Flv.RuleTests/Integrated/TestBase.cs new file mode 100644 index 0000000..4eb3a14 --- /dev/null +++ b/test/BililiveRecorder.Flv.RuleTests/Integrated/TestBase.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using BililiveRecorder.Flv.Pipeline; +using BililiveRecorder.Flv.Writer; +using BililiveRecorder.Flv.Xml; +using Microsoft.Extensions.DependencyInjection; + +namespace BililiveRecorder.Flv.RuleTests.Integrated +{ + public abstract class TestBase + { + protected XmlFlvFile LoadFile(string path) => + (XmlFlvFile)XmlFlvFile.Serializer.Deserialize(File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)); + + protected ProcessingDelegate BuildPipeline() => + new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).AddDefault().AddRemoveFillerData().Build(); + + protected async Task RunPipeline(ITagGroupReader reader, IFlvTagWriter output, List comments) + { + var writer = new FlvProcessingContextWriter(output); + var session = new Dictionary(); + var context = new FlvProcessingContext(); + var pipeline = this.BuildPipeline(); + + while (true) + { + var group = await reader.ReadGroupAsync(default).ConfigureAwait(false); + + if (group is null) + break; + + context.Reset(group, session); + await pipeline(context).ConfigureAwait(false); + + comments.AddRange(context.Comments); + await writer.WriteAsync(context).ConfigureAwait(false); + } + } + } +} diff --git a/test/BililiveRecorder.Flv.RuleTests/SampleDirectoryTestDataAttribute.cs b/test/BililiveRecorder.Flv.RuleTests/SampleDirectoryTestDataAttribute.cs new file mode 100644 index 0000000..725e5eb --- /dev/null +++ b/test/BililiveRecorder.Flv.RuleTests/SampleDirectoryTestDataAttribute.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Xunit.Sdk; + +namespace BililiveRecorder.Flv.RuleTests +{ + public class SampleDirectoryTestDataAttribute : DataAttribute + { + public SampleDirectoryTestDataAttribute(string basePath) + { + this.BasePath = basePath; + } + + public string BasePath { get; } + + public override IEnumerable GetData(MethodInfo testMethod) + { + var fullPath = Path.IsPathRooted(this.BasePath) ? this.BasePath : Path.GetRelativePath(Directory.GetCurrentDirectory(), this.BasePath); + + if (!Directory.Exists(fullPath)) + throw new ArgumentException($"Could not find directory at path: {fullPath}"); + + return Directory.GetDirectories(fullPath).Select(x => new object[] { x }); + } + } +} diff --git a/test/BililiveRecorder.Flv.RuleTests/SampleFileTestDataAttribute.cs b/test/BililiveRecorder.Flv.RuleTests/SampleFileTestDataAttribute.cs new file mode 100644 index 0000000..f61de91 --- /dev/null +++ b/test/BililiveRecorder.Flv.RuleTests/SampleFileTestDataAttribute.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Xunit.Sdk; + +namespace BililiveRecorder.Flv.RuleTests +{ + public class SampleFileTestDataAttribute : DataAttribute + { + public SampleFileTestDataAttribute(string basePath) + { + this.BasePath = basePath; + } + + public string BasePath { get; } + + public override IEnumerable GetData(MethodInfo testMethod) + { + var fullPath = Path.IsPathRooted(this.BasePath) ? this.BasePath : Path.GetRelativePath(Directory.GetCurrentDirectory(), this.BasePath); + + if (!Directory.Exists(fullPath)) + throw new ArgumentException($"Could not find directory at path: {fullPath}"); + + return Directory.GetFiles(fullPath, "*.xml").Select(x => new object[] { x }); + } + } +} diff --git a/test/BililiveRecorder.Flv.RuleTests/Standalone/Test.cs b/test/BililiveRecorder.Flv.RuleTests/Standalone/Test.cs new file mode 100644 index 0000000..df7729a --- /dev/null +++ b/test/BililiveRecorder.Flv.RuleTests/Standalone/Test.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BililiveRecorder.Flv.RuleTests.Standalone +{ + public class Test + { + + } +} diff --git a/test/BililiveRecorder.Flv.RuleTests/samples/bad/.gitkeep b/test/BililiveRecorder.Flv.RuleTests/samples/bad/.gitkeep new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/test/BililiveRecorder.Flv.RuleTests/samples/bad/.gitkeep @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/BililiveRecorder.Flv.RuleTests/samples/good-relax/.gitkeep b/test/BililiveRecorder.Flv.RuleTests/samples/good-relax/.gitkeep new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/test/BililiveRecorder.Flv.RuleTests/samples/good-relax/.gitkeep @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/.gitkeep b/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/.gitkeep new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/.gitkeep @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps10.xml b/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps10.xml new file mode 100644 index 0000000..b8d16a4 --- /dev/null +++ b/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps10.xml @@ -0,0 +1,295 @@ + + + + + [{"Type":"String","Value":"onMetaData"},{"Type":"EcmaArray","Value":{"duration":{"Type":"Number","Value":3.9},"width":{"Type":"Number","Value":1920.0},"height":{"Type":"Number","Value":1080.0},"videodatarate":{"Type":"Number","Value":1464.84375},"framerate":{"Type":"Number","Value":10.0},"videocodecid":{"Type":"Number","Value":7.0},"audiodatarate":{"Type":"Number","Value":156.25},"audiosamplerate":{"Type":"Number","Value":48000.0},"audiosamplesize":{"Type":"Number","Value":16.0},"stereo":{"Type":"Boolean","Value":true},"audiocodecid":{"Type":"Number","Value":10.0},"encoder":{"Type":"String","Value":"Lavf58.29.100"},"filesize":{"Type":"Number","Value":9462.0}}}] + + + 170000000001640028FFE1001E67640028ACD940780227E59A808080A000000300200000030291E30632C001000468EF8FCB + + + AF00119056E500 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps2997.xml b/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps2997.xml new file mode 100644 index 0000000..3fd6dcb --- /dev/null +++ b/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps2997.xml @@ -0,0 +1,715 @@ + + + + + [{"Type":"String","Value":"onMetaData"},{"Type":"EcmaArray","Value":{"duration":{"Type":"Number","Value":5.172},"width":{"Type":"Number","Value":1920.0},"height":{"Type":"Number","Value":1080.0},"videodatarate":{"Type":"Number","Value":1464.84375},"framerate":{"Type":"Number","Value":29.970029970029969},"videocodecid":{"Type":"Number","Value":7.0},"audiodatarate":{"Type":"Number","Value":156.25},"audiosamplerate":{"Type":"Number","Value":48000.0},"audiosamplesize":{"Type":"Number","Value":16.0},"stereo":{"Type":"Boolean","Value":true},"audiocodecid":{"Type":"Number","Value":10.0},"encoder":{"Type":"String","Value":"Lavf58.29.100"},"filesize":{"Type":"Number","Value":21485.0}}}] + + + 170000000001640028FFE1001C67640028ACD940780227E59A808080A000007D20001D4C11E30632C001000468EF8FCB + + + AF00119056E500 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps30.xml b/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps30.xml new file mode 100644 index 0000000..76d0b29 --- /dev/null +++ b/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps30.xml @@ -0,0 +1,473 @@ + + + + + [{"Type":"String","Value":"onMetaData"},{"Type":"EcmaArray","Value":{"duration":{"Type":"Number","Value":3.433},"width":{"Type":"Number","Value":1920.0},"height":{"Type":"Number","Value":1080.0},"videodatarate":{"Type":"Number","Value":1464.84375},"framerate":{"Type":"Number","Value":30.0},"videocodecid":{"Type":"Number","Value":7.0},"audiodatarate":{"Type":"Number","Value":156.25},"audiosamplerate":{"Type":"Number","Value":48000.0},"audiosamplesize":{"Type":"Number","Value":16.0},"stereo":{"Type":"Boolean","Value":true},"audiocodecid":{"Type":"Number","Value":10.0},"encoder":{"Type":"String","Value":"Lavf58.29.100"},"filesize":{"Type":"Number","Value":14836.0}}}] + + + 170000000001640028FFE1001D67640028ACD940780227E59A808080A0000003002000000791E30632C001000468EF8FCB + + + AF00119056E500 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps5994.xml b/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps5994.xml new file mode 100644 index 0000000..103b56b --- /dev/null +++ b/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps5994.xml @@ -0,0 +1,612 @@ + + + + + [{"Type":"String","Value":"onMetaData"},{"Type":"EcmaArray","Value":{"duration":{"Type":"Number","Value":2.653},"width":{"Type":"Number","Value":1920.0},"height":{"Type":"Number","Value":1080.0},"videodatarate":{"Type":"Number","Value":1464.84375},"framerate":{"Type":"Number","Value":59.940059940059939},"videocodecid":{"Type":"Number","Value":7.0},"audiodatarate":{"Type":"Number","Value":156.25},"audiosamplerate":{"Type":"Number","Value":48000.0},"audiosamplesize":{"Type":"Number","Value":16.0},"stereo":{"Type":"Boolean","Value":true},"audiocodecid":{"Type":"Number","Value":10.0},"encoder":{"Type":"String","Value":"Lavf58.29.100"},"filesize":{"Type":"Number","Value":19080.0}}}] + + + 17000000000164002AFFE1001C6764002AACD940780227E59A808080A000007D20003A9811E30632C001000468EF8FCB + + + AF00119056E500 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps60.xml b/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps60.xml new file mode 100644 index 0000000..2cbf419 --- /dev/null +++ b/test/BililiveRecorder.Flv.RuleTests/samples/good-strict/obs-fps60.xml @@ -0,0 +1,952 @@ + + + + + [{"Type":"String","Value":"onMetaData"},{"Type":"EcmaArray","Value":{"duration":{"Type":"Number","Value":4.183},"width":{"Type":"Number","Value":1920.0},"height":{"Type":"Number","Value":1080.0},"videodatarate":{"Type":"Number","Value":1464.84375},"framerate":{"Type":"Number","Value":60.0},"videocodecid":{"Type":"Number","Value":7.0},"audiodatarate":{"Type":"Number","Value":156.25},"audiosamplerate":{"Type":"Number","Value":48000.0},"audiosamplesize":{"Type":"Number","Value":16.0},"stereo":{"Type":"Boolean","Value":true},"audiocodecid":{"Type":"Number","Value":10.0},"encoder":{"Type":"String","Value":"Lavf58.29.100"},"filesize":{"Type":"Number","Value":28683.0}}}] + + + 17000000000164002AFFE1001D6764002AACD940780227E59A808080A0000003002000000F11E30632C001000468EF8FCB + + + AF00119056E500 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/BililiveRecorder.Flv.UnitTests/BililiveRecorder.Flv.UnitTests.csproj b/test/BililiveRecorder.Flv.UnitTests/BililiveRecorder.Flv.UnitTests.csproj index 2d6a8f5..c1c6f3f 100644 --- a/test/BililiveRecorder.Flv.UnitTests/BililiveRecorder.Flv.UnitTests.csproj +++ b/test/BililiveRecorder.Flv.UnitTests/BililiveRecorder.Flv.UnitTests.csproj @@ -11,7 +11,7 @@ - + all diff --git a/test/BililiveRecorder.Flv.UnitTests/Grouping/GroupingTest.cs b/test/BililiveRecorder.Flv.UnitTests/Grouping/GroupingTest.cs index ffe99e9..fd9947c 100644 --- a/test/BililiveRecorder.Flv.UnitTests/Grouping/GroupingTest.cs +++ b/test/BililiveRecorder.Flv.UnitTests/Grouping/GroupingTest.cs @@ -21,7 +21,6 @@ namespace BililiveRecorder.Flv.UnitTests.Grouping { public Stream CreateAlternativeHeaderStream() => throw new NotImplementedException(); public (Stream, object) CreateOutputStream() => (File.Open(Path.Combine(TEST_OUTPUT_PATH, DateTimeOffset.Now.ToString("s").Replace(':', '-') + ".flv"), FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None), null); - public bool ShouldCreateNewFile(Stream outputStream, IList tags) => false; } [Fact(Skip = "Not ready")] @@ -81,7 +80,7 @@ namespace BililiveRecorder.Flv.UnitTests.Grouping var sp = new ServiceCollection().BuildServiceProvider(); var pipeline = new ProcessingPipelineBuilder(sp).AddDefault().AddRemoveFillerData().Build(); - using var writer = new FlvProcessingContextWriter(new TestOutputProvider(), new TestRecyclableMemoryStreamProvider(), null); + using var writer = new FlvProcessingContextWriter(new FlvTagFileWriter(new TestOutputProvider(), new TestRecyclableMemoryStreamProvider(), null)); while (true) {