add loudness normalization (#129)

* fix music video search result artwork

* add normalize loudness setting

* fix audio normalization

* fix music video thumbnails in collection items view

* fix ef core warnings querying playout item

* implement audio loudness normalization filter
This commit is contained in:
Jason Dove
2021-04-03 13:36:11 -05:00
committed by GitHub
parent d4a2197dfa
commit 74c95249c3
22 changed files with 2289 additions and 404 deletions
@@ -16,6 +16,7 @@ namespace ErsatzTV.Core.FFmpeg
private Option<string> _frameRate = None;
private Option<HardwareAccelerationKind> _hardwareAccelerationKind = None;
private string _inputCodec;
private bool _normalizeLoudness;
private Option<IDisplaySize> _padToSize = None;
private Option<IDisplaySize> _scaleToSize = None;
@@ -49,6 +50,12 @@ namespace ErsatzTV.Core.FFmpeg
return this;
}
public FFmpegComplexFilterBuilder WithNormalizeLoudness(bool normalizeLoudness)
{
_normalizeLoudness = normalizeLoudness;
return this;
}
public FFmpegComplexFilterBuilder WithInputCodec(string codec)
{
_inputCodec = codec;
@@ -77,22 +84,22 @@ namespace ErsatzTV.Core.FFmpeg
_ => false
};
_audioDuration.IfSome(
audioDuration =>
{
complexFilter.Append($"[{audioLabel}]");
complexFilter.Append($"apad=whole_dur={audioDuration.TotalMilliseconds}ms");
audioLabel = "[a]";
complexFilter.Append(audioLabel);
});
var audioFilterQueue = new List<string>();
var videoFilterQueue = new List<string>();
var filterQueue = new List<string>();
if (_normalizeLoudness)
{
audioFilterQueue.Add("loudnorm=I=-16:TP=-1.5:LRA=11");
}
_audioDuration.IfSome(
audioDuration => audioFilterQueue.Add($"apad=whole_dur={audioDuration.TotalMilliseconds}ms"));
bool usesHardwareFilters = acceleration != HardwareAccelerationKind.None && !isHardwareDecode &&
(_deinterlace || _scaleToSize.IsSome);
if (usesHardwareFilters)
{
filterQueue.Add("hwupload");
videoFilterQueue.Add("hwupload");
}
if (_deinterlace)
@@ -107,11 +114,11 @@ namespace ErsatzTV.Core.FFmpeg
if (!string.IsNullOrWhiteSpace(filter))
{
filterQueue.Add(filter);
videoFilterQueue.Add(filter);
}
}
_frameRate.IfSome(frameRate => filterQueue.Add($"fps=fps={frameRate}"));
_frameRate.IfSome(frameRate => videoFilterQueue.Add($"fps=fps={frameRate}"));
_scaleToSize.IfSome(
size =>
@@ -126,7 +133,7 @@ namespace ErsatzTV.Core.FFmpeg
if (!string.IsNullOrWhiteSpace(filter))
{
filterQueue.Add(filter);
videoFilterQueue.Add(filter);
}
});
@@ -134,19 +141,19 @@ namespace ErsatzTV.Core.FFmpeg
{
if (acceleration != HardwareAccelerationKind.None && (isHardwareDecode || usesHardwareFilters))
{
filterQueue.Add("hwdownload");
videoFilterQueue.Add("hwdownload");
string format = acceleration switch
{
HardwareAccelerationKind.Vaapi => "format=nv12|vaapi",
_ => "format=nv12"
};
filterQueue.Add(format);
videoFilterQueue.Add(format);
}
filterQueue.Add("setsar=1");
videoFilterQueue.Add("setsar=1");
}
_padToSize.IfSome(size => filterQueue.Add($"pad={size.Width}:{size.Height}:(ow-iw)/2:(oh-ih)/2"));
_padToSize.IfSome(size => videoFilterQueue.Add($"pad={size.Width}:{size.Height}:(ow-iw)/2:(oh-ih)/2"));
if ((_scaleToSize.IsSome || _padToSize.IsSome) && acceleration != HardwareAccelerationKind.None)
{
@@ -155,19 +162,27 @@ namespace ErsatzTV.Core.FFmpeg
HardwareAccelerationKind.Qsv => "hwupload=extra_hw_frames=64",
_ => "hwupload"
};
filterQueue.Add(upload);
videoFilterQueue.Add(upload);
}
if (filterQueue.Any())
bool hasAudioFilters = audioFilterQueue.Any();
if (hasAudioFilters)
{
// TODO: any audio filter
if (_audioDuration.IsSome)
complexFilter.Append($"[{audioLabel}]");
complexFilter.Append(string.Join(",", audioFilterQueue));
audioLabel = "[a]";
complexFilter.Append(audioLabel);
}
if (videoFilterQueue.Any())
{
if (hasAudioFilters)
{
complexFilter.Append(';');
}
complexFilter.Append($"[{videoLabel}]");
complexFilter.Append(string.Join(",", filterQueue));
complexFilter.Append(string.Join(",", videoFilterQueue));
videoLabel = "[v]";
complexFilter.Append(videoLabel);
}
@@ -28,5 +28,6 @@ namespace ErsatzTV.Core.FFmpeg
public bool Deinterlace { get; set; }
public Option<string> FrameRate { get; set; }
public Option<int> VideoTrackTimeScale { get; set; }
public bool NormalizeLoudness { get; set; }
}
}
@@ -107,22 +107,20 @@ namespace ErsatzTV.Core.FFmpeg
result.VideoCodec = "copy";
}
if (NeedToNormalizeAudioCodec(ffmpegProfile, audioStream))
if (ffmpegProfile.NormalizeAudio)
{
result.AudioCodec = ffmpegProfile.AudioCodec;
result.AudioBitrate = ffmpegProfile.AudioBitrate;
result.AudioBufferSize = ffmpegProfile.AudioBufferSize;
if (ffmpegProfile.NormalizeAudio)
if (audioStream.Channels != ffmpegProfile.AudioChannels)
{
if (audioStream.Channels != ffmpegProfile.AudioChannels)
{
result.AudioChannels = ffmpegProfile.AudioChannels;
}
result.AudioSampleRate = ffmpegProfile.AudioSampleRate;
result.AudioDuration = version.Duration;
result.AudioChannels = ffmpegProfile.AudioChannels;
}
result.AudioSampleRate = ffmpegProfile.AudioSampleRate;
result.AudioDuration = version.Duration;
result.NormalizeLoudness = ffmpegProfile.NormalizeLoudness;
}
else
{
@@ -170,9 +168,6 @@ namespace ErsatzTV.Core.FFmpeg
private static bool NeedToNormalizeVideoCodec(FFmpegProfile ffmpegProfile, MediaStream videoStream) =>
ffmpegProfile.NormalizeVideo && ffmpegProfile.VideoCodec != videoStream.Codec;
private static bool NeedToNormalizeAudioCodec(FFmpegProfile ffmpegProfile, MediaStream audioStream) =>
ffmpegProfile.NormalizeAudio && ffmpegProfile.AudioCodec != audioStream.Codec;
private static IDisplaySize CalculateScaledSize(FFmpegProfile ffmpegProfile, MediaVersion version)
{
IDisplaySize sarSize = SARSize(version);
@@ -324,6 +324,12 @@ namespace ErsatzTV.Core.FFmpeg
return this;
}
public FFmpegProcessBuilder WithNormalizeLoudness(bool normalizeLoudness)
{
_complexFilterBuilder = _complexFilterBuilder.WithNormalizeLoudness(normalizeLoudness);
return this;
}
public FFmpegProcessBuilder WithFrameRate(Option<string> frameRate)
{
_complexFilterBuilder = _complexFilterBuilder.WithFrameRate(frameRate);
+3 -4
View File
@@ -50,7 +50,9 @@ namespace ErsatzTV.Core.FFmpeg
.WithSeek(playbackSettings.StreamSeek)
.WithInputCodec(path, playbackSettings.HardwareAcceleration, videoStream.Codec)
.WithFrameRate(playbackSettings.FrameRate)
.WithVideoTrackTimeScale(playbackSettings.VideoTrackTimeScale);
.WithVideoTrackTimeScale(playbackSettings.VideoTrackTimeScale)
.WithAlignedAudio(playbackSettings.AudioDuration)
.WithNormalizeLoudness(playbackSettings.NormalizeLoudness);
playbackSettings.ScaledSize.Match(
scaledSize =>
@@ -65,7 +67,6 @@ namespace ErsatzTV.Core.FFmpeg
}
builder = builder
.WithAlignedAudio(playbackSettings.AudioDuration)
.WithFilterComplex(videoStream.Index, audioStream.Index);
},
() =>
@@ -75,7 +76,6 @@ namespace ErsatzTV.Core.FFmpeg
builder = builder
.WithDeinterlace(playbackSettings.Deinterlace)
.WithBlackBars(channel.FFmpegProfile.Resolution)
.WithAlignedAudio(playbackSettings.AudioDuration)
.WithFilterComplex(videoStream.Index, audioStream.Index);
}
else if (playbackSettings.Deinterlace)
@@ -87,7 +87,6 @@ namespace ErsatzTV.Core.FFmpeg
else
{
builder = builder
.WithAlignedAudio(playbackSettings.AudioDuration)
.WithFilterComplex(videoStream.Index, audioStream.Index);
}
});