From 40c14b573ab5aaa42851b5b4e8b170efb7744f54 Mon Sep 17 00:00:00 2001 From: alex6dj Date: Mon, 4 Oct 2021 19:10:00 -0400 Subject: [PATCH] Implemented Dynamic Audio Normalizer --- FFMpegCore.Test/ArgumentBuilderTest.cs | 22 +++++++++ FFMpegCore.Test/AudioTest.cs | 48 +++++++++++++++++- .../FFMpeg/Arguments/AudioFiltersArgument.cs | 6 +++ .../Arguments/DynamicNormalizerArgument.cs | 49 +++++++++++++++++++ 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 FFMpegCore/FFMpeg/Arguments/DynamicNormalizerArgument.cs diff --git a/FFMpegCore.Test/ArgumentBuilderTest.cs b/FFMpegCore.Test/ArgumentBuilderTest.cs index 140e033..a06a951 100644 --- a/FFMpegCore.Test/ArgumentBuilderTest.cs +++ b/FFMpegCore.Test/ArgumentBuilderTest.cs @@ -447,5 +447,27 @@ public void Builder_BuildString_PanAudioFilterChannelNoOutputDefinition() Assert.AreEqual("-i \"input.mp4\" -af \"pan=stereo\" \"output.mp4\"", str); } + + [TestMethod] + public void Builder_BuildString_DynamicAudioNormalizerDefaultFormat() + { + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, + opt => opt.WithAudioFilters(filterOptions => filterOptions.DynamicNormalizer())) + .Arguments; + + Assert.AreEqual("-i \"input.mp4\" -af \"dynaudnorm=f=500:g=31:p=0.95:m=10.0:r=0.0:n=1:c=0:b=0:s=0.0\" \"output.mp4\"", str); + } + + [TestMethod] + public void Builder_BuildString_DynamicAudioNormalizerWithValuesFormat() + { + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, + opt => opt.WithAudioFilters(filterOptions => filterOptions.DynamicNormalizer(125, 13, 0.9215, 5.124, 0.5458,false,true,true, 0.3333333))) + .Arguments; + + Assert.AreEqual("-i \"input.mp4\" -af \"dynaudnorm=f=125:g=13:p=0.92:m=5.1:r=0.5:n=0:c=1:b=1:s=0.3\" \"output.mp4\"", str); + } } } \ No newline at end of file diff --git a/FFMpegCore.Test/AudioTest.cs b/FFMpegCore.Test/AudioTest.cs index 1231b6f..9c3d074 100644 --- a/FFMpegCore.Test/AudioTest.cs +++ b/FFMpegCore.Test/AudioTest.cs @@ -1,5 +1,6 @@ using FFMpegCore.Enums; using FFMpegCore.Exceptions; +using FFMpegCore.Extend; using FFMpegCore.Pipes; using FFMpegCore.Test.Resources; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -8,7 +9,6 @@ using System.IO; using System.Linq; using System.Threading.Tasks; -using FFMpegCore.Extend; namespace FFMpegCore.Test { @@ -281,5 +281,51 @@ public void Audio_Pan_ToMonoChannelsLayoutToOutputDefinitionsMismatch() .WithAudioFilters(filter => filter.Pan("mono", "c0=c0", "c1=c1"))) .ProcessSynchronously()); } + + [TestMethod, Timeout(10000)] + public void Audio_DynamicNormalizer_WithDefaultValues() + { + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + var success = FFMpegArguments.FromFileInput(TestResources.Mp3Audio) + .OutputToFile(outputFile, true, + argumentOptions => argumentOptions + .WithAudioFilters(filter => filter.DynamicNormalizer())) + .ProcessSynchronously(); + + Assert.IsTrue(success); + } + + [TestMethod, Timeout(10000)] + public void Audio_DynamicNormalizer_WithNonDefaultValues() + { + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + var success = FFMpegArguments.FromFileInput(TestResources.Mp3Audio) + .OutputToFile(outputFile, true, + argumentOptions => argumentOptions + .WithAudioFilters( + filter => filter.DynamicNormalizer(250, 7, 0.9, 2, 1, false, true, true, 0.5))) + .ProcessSynchronously(); + + Assert.IsTrue(success); + } + + [DataTestMethod, Timeout(10000)] + [DataRow(2)] + [DataRow(32)] + [DataRow(8)] + public void Audio_DynamicNormalizer_FilterWindow(int filterWindow) + { + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + var ex = Assert.ThrowsException(() => FFMpegArguments + .FromFileInput(TestResources.Mp3Audio) + .OutputToFile(outputFile, true, + argumentOptions => argumentOptions + .WithAudioFilters( + filter => filter.DynamicNormalizer(filterWindow: filterWindow))) + .ProcessSynchronously()); + } } } \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/Arguments/AudioFiltersArgument.cs b/FFMpegCore/FFMpeg/Arguments/AudioFiltersArgument.cs index 4776d81..50b26b3 100644 --- a/FFMpegCore/FFMpeg/Arguments/AudioFiltersArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/AudioFiltersArgument.cs @@ -44,6 +44,12 @@ public class AudioFilterOptions public AudioFilterOptions Pan(string channelLayout, params string[] outputDefinitions) => WithArgument(new PanArgument(channelLayout, outputDefinitions)); public AudioFilterOptions Pan(int channels, params string[] outputDefinitions) => WithArgument(new PanArgument(channels, outputDefinitions)); + public AudioFilterOptions DynamicNormalizer(int frameLength = 500, int filterWindow = 31, double targetPeak = 0.95, + double gainFactor = 10.0, double targetRms = 0.0, bool channelCoupling = true, + bool enableDcBiasCorrection = false, bool enableAlternativeBoundary = false, + double compressorFactor = 0.0) => WithArgument(new DynamicNormalizerArgument(frameLength, filterWindow, + targetPeak, gainFactor, targetRms, channelCoupling, enableDcBiasCorrection, enableAlternativeBoundary, + compressorFactor)); private AudioFilterOptions WithArgument(IAudioFilterArgument argument) { diff --git a/FFMpegCore/FFMpeg/Arguments/DynamicNormalizerArgument.cs b/FFMpegCore/FFMpeg/Arguments/DynamicNormalizerArgument.cs new file mode 100644 index 0000000..d1c948c --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/DynamicNormalizerArgument.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace FFMpegCore.Arguments +{ + public class DynamicNormalizerArgument : IAudioFilterArgument + { + private readonly Dictionary _arguments = new Dictionary(); + + /// + /// Dynamic Audio Normalizer. + /// + /// Set the frame length in milliseconds. Must be between 10 to 8000. The default value is 500 + /// Set the Gaussian filter window size. In range from 3 to 301, must be odd number. The default value is 31 + /// Set the target peak value. The default value is 0.95 + /// Set the maximum gain factor. In range from 1.0 to 100.0. Default is 10.0. + /// Set the target RMS. In range from 0.0 to 1.0. Default to 0.0 (disabled) + /// Enable channels coupling. By default is enabled. + /// Enable DC bias correction. By default is disabled. + /// Enable alternative boundary mode. By default is disabled. + /// Set the compress factor. In range from 0.0 to 30.0. Default is 0.0 (disabled). + public DynamicNormalizerArgument(int frameLength = 500, int filterWindow = 31, double targetPeak = 0.95, double gainFactor = 10.0, double targetRms = 0.0, bool channelCoupling = true, bool enableDcBiasCorrection = false, bool enableAlternativeBoundary = false, double compressorFactor = 0.0) + { + if (frameLength < 10 || frameLength > 8000) throw new ArgumentOutOfRangeException(nameof(frameLength),"Frame length must be between 10 to 8000"); + if (filterWindow < 3 || filterWindow > 31) throw new ArgumentOutOfRangeException(nameof(filterWindow), "Gaussian filter window size must be between 3 to 31"); + if (filterWindow % 2 == 0) throw new ArgumentOutOfRangeException(nameof(filterWindow), "Gaussian filter window size must be an odd number"); + if (targetPeak <= 0 || targetPeak > 1) throw new ArgumentOutOfRangeException(nameof(targetPeak)); + if (gainFactor < 1 || gainFactor > 100) throw new ArgumentOutOfRangeException(nameof(gainFactor), "Gain factor must be between 1.0 to 100.0"); + if (targetRms < 0 || targetRms > 1) throw new ArgumentOutOfRangeException(nameof(targetRms), "Target RMS must be between 0.0 and 1.0"); + if (compressorFactor < 0 || compressorFactor > 30) throw new ArgumentOutOfRangeException(nameof(compressorFactor), "Compressor factor must be between 0.0 and 30.0"); + + _arguments.Add("f", frameLength.ToString()); + _arguments.Add("g", filterWindow.ToString()); + _arguments.Add("p", targetPeak.ToString("0.00", CultureInfo.InvariantCulture)); + _arguments.Add("m", gainFactor.ToString("0.0", CultureInfo.InvariantCulture)); + _arguments.Add("r", targetRms.ToString("0.0", CultureInfo.InvariantCulture)); + _arguments.Add("n", (channelCoupling ? 1 : 0).ToString()); + _arguments.Add("c", (enableDcBiasCorrection ? 1 : 0).ToString()); + _arguments.Add("b", (enableAlternativeBoundary ? 1 : 0).ToString()); + _arguments.Add("s", compressorFactor.ToString("0.0", CultureInfo.InvariantCulture)); + } + + public string Key { get; } = "dynaudnorm"; + + public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}")); + } +} \ No newline at end of file