From 6b0ced6be9e4eec571a6cc0a240f053f52da45f3 Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Mon, 28 Feb 2022 14:21:42 -0600 Subject: [PATCH] fix nvidia watermark (#660) * add watermark transcoding tests * nvidia fixes * update changelog --- CHANGELOG.md | 2 + .../ErsatzTV.Core.Tests.csproj | 6 ++ .../FFmpeg/TranscodingTests.cs | 78 +++++++++++++++++- ErsatzTV.Core.Tests/Resources/ErsatzTV.png | Bin 0 -> 1615 bytes ErsatzTV.FFmpeg/Decoder/AvailableDecoders.cs | 9 +- .../Decoder/Cuvid/DecoderMpeg2Cuvid.cs | 10 ++- ErsatzTV.FFmpeg/PipelineBuilder.cs | 5 +- 7 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 ErsatzTV.Core.Tests/Resources/ErsatzTV.png diff --git a/CHANGELOG.md b/CHANGELOG.md index b040c44d2..d6af852a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Fixed - Fix song sorting with `Chronological` and `Shuffle In Order` playback orders +- Fix watermark on scaled and/or padded video with NVIDIA acceleration +- Fix playback of interlaced mpeg2video content with NVIDIA acceleration ## [0.4.2-alpha] - 2022-02-26 ### Fixed diff --git a/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj b/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj index 1ffa8117e..ad152d802 100644 --- a/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj +++ b/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj @@ -29,4 +29,10 @@ + + + Always + + + diff --git a/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs b/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs index 6949f6adf..3edbff92f 100644 --- a/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs +++ b/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs @@ -47,8 +47,25 @@ namespace ErsatzTV.Core.Tests.FFmpeg WithPadding } + public enum Watermark + { + None, + PermanentOpaque, + PermanentTransparent, + IntermittentOpaque, + IntermittentTransparent + // TODO: animated vs static + } + private class TestData { + public static Watermark[] Watermarks = + { + Watermark.None, + Watermark.PermanentOpaque, + Watermark.PermanentTransparent + }; + public static Padding[] Paddings = { Padding.NoPadding, @@ -160,6 +177,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg Padding padding, [ValueSource(typeof(TestData), nameof(TestData.VideoScanKinds))] VideoScanKind videoScanKind, + [ValueSource(typeof(TestData), nameof(TestData.Watermarks))] + Watermark watermark, // [ValueSource(typeof(TestData), nameof(TestData.SoftwareCodecs))] string profileCodec, // [ValueSource(typeof(TestData), nameof(TestData.NoAcceleration))] HardwareAccelerationKind profileAcceleration) [ValueSource(typeof(TestData), nameof(TestData.NvidiaCodecs))] string profileCodec, @@ -209,10 +228,20 @@ namespace ErsatzTV.Core.Tests.FFmpeg p1.ExitCode.Should().Be(0); } + var imageCache = new Mock(); + + // always return the static watermark resource + imageCache.Setup( + ic => ic.GetPathForImage( + It.IsAny(), + It.Is(x => x == ArtworkKind.Watermark), + It.IsAny>())) + .Returns(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "ErsatzTV.png")); + var oldService = new FFmpegProcessService( new FFmpegPlaybackSettingsCalculator(), new FakeStreamSelector(), - new Mock().Object, + imageCache.Object, new Mock().Object, new Mock>().Object); @@ -262,6 +291,51 @@ namespace ErsatzTV.Core.Tests.FFmpeg DateTimeOffset now = DateTimeOffset.Now; + Option channelWatermark = Option.None; + switch (watermark) + { + case Watermark.None: + break; + case Watermark.IntermittentOpaque: + channelWatermark = new ChannelWatermark + { + ImageSource = ChannelWatermarkImageSource.Custom, + Mode = ChannelWatermarkMode.Intermittent, + // TODO: how do we make sure this actually appears + FrequencyMinutes = 1, + DurationSeconds = 2, + Opacity = 100 + }; + break; + case Watermark.IntermittentTransparent: + channelWatermark = new ChannelWatermark + { + ImageSource = ChannelWatermarkImageSource.Custom, + Mode = ChannelWatermarkMode.Intermittent, + // TODO: how do we make sure this actually appears + FrequencyMinutes = 1, + DurationSeconds = 2, + Opacity = 80 + }; + break; + case Watermark.PermanentOpaque: + channelWatermark = new ChannelWatermark + { + ImageSource = ChannelWatermarkImageSource.Custom, + Mode = ChannelWatermarkMode.Permanent, + Opacity = 100 + }; + break; + case Watermark.PermanentTransparent: + channelWatermark = new ChannelWatermark + { + ImageSource = ChannelWatermarkImageSource.Custom, + Mode = ChannelWatermarkMode.Permanent, + Opacity = 80 + }; + break; + } + Process process = await service.ForPlayoutItem( "ffmpeg", false, @@ -283,7 +357,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg now, now + TimeSpan.FromSeconds(5), now, - None, + channelWatermark, VaapiDriver.Default, "/dev/dri/renderD128", false, diff --git a/ErsatzTV.Core.Tests/Resources/ErsatzTV.png b/ErsatzTV.Core.Tests/Resources/ErsatzTV.png new file mode 100644 index 0000000000000000000000000000000000000000..b053a5a66c13fdfb41801df374cecfcde4fa1e31 GIT binary patch literal 1615 zcmZvcdpOg39LIl>C8M)4LdhlCx-8|EmL)q|!i=R>{U|l9Ham2bZq(dz$?ddqITUGW z!iH&z3hkgBrowDup-6HU8M$>Z z4Iqfm0H7A`jC0&~p?$bx@cLa_nA*_!(ckUG2`h957M4e!e|UH- zW!e3xYd0LG7|wHUrTbieMoeN{e6USA(;k;wk(cWCYh;~gF67?>f5|Eu=Ca^+6?I|J zaD!d3=KMjUa&lX?)u#R>sq8LG7Q-Bau1;Vc)tS!YBpilv5D23KiII9wWJ_`UazRUv zrCltaVZI$LVg-9+mQg-~TjC5q6%$vkuK8HH2jNUtHHmFv2$8l3hn|=Nu%6a>`$UtM z$vX`ybQ35p==>FDQERc#-Td*riaBkZ3OHuZs3)mRaNLj6gh&G;s{1+|bT@17TuY9z zOCy$)19vcV^A01&v+cMRA(mT}+sdXLYrpiGIYDuJq4s^%Pt6ENXs6D*#eOc_C1%P; z{^{-`K-rqSW4(H84%6p~?&FQ@Zjr|WF9P)r|xJG@J zkErytVE3b?Ev$(gucTB=oSP>)1rxcCmuzo_NW*+R0wVXsr{ukonrijAMhJGnH>Q=G zyjCFHPRQnPNg2kxhv$&Y4dh_+Nz@in%EWDj=JisvVsU=OP##zZ+Jr1B?d#Z$r-vgK zj?9JGs6_t3OWGce9*aN{3QiES&ozA7WZz*l*j@=5$LWvuto7L#hnN+-9G5Tlt2>M_ zQ8s0UK1Xf3R}sbh0C(7a{^9-%vS>YX!-(ZNI%wlNx#tD*DJsAr)@puoM>TWEgeY!w z>cfPf3Ae~@m9N?k#OK5`mN^3dsLQih6_is0JiHALC=~u&~gAiginP?I4Z%aw1PKb4p z#E>91sBIls-lO1fJ$Rag><V>%uf=$(y&!k_!(M3eUh)8(oTJXDUZm;I+eLnI*#hrK%jv;q=gV`mtn7e~!%zl(GeUBqDba;!pCLykM%$etn4oB&mTS=?)AOpnV)Qoj1~p` zaddmTeDdZRTb ForVideoFormat(FFmpegState ffmpegState, FrameState currentState, ILogger logger) + public static Option ForVideoFormat( + FFmpegState ffmpegState, + FrameState currentState, + FrameState desiredState, + ILogger logger) { return (ffmpegState.HardwareAccelerationMode, currentState.VideoFormat, currentState.PixelFormat.Match(pf => pf.Name, () => string.Empty)) switch @@ -20,7 +24,8 @@ public static class AvailableDecoders => new DecoderH264(), (HardwareAccelerationMode.Nvenc, VideoFormat.H264, _) => new DecoderH264Cuvid(), - (HardwareAccelerationMode.Nvenc, VideoFormat.Mpeg2Video, _) => new DecoderMpeg2Cuvid(), + (HardwareAccelerationMode.Nvenc, VideoFormat.Mpeg2Video, _) => new DecoderMpeg2Cuvid( + desiredState.Deinterlaced), (HardwareAccelerationMode.Nvenc, VideoFormat.Vc1, _) => new DecoderVc1Cuvid(), (HardwareAccelerationMode.Nvenc, VideoFormat.Vp9, _) => new DecoderVp9Cuvid(), (HardwareAccelerationMode.Nvenc, VideoFormat.Mpeg4, _) => new DecoderMpeg4Cuvid(), diff --git a/ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg2Cuvid.cs b/ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg2Cuvid.cs index fa9e209c6..980eff9e1 100644 --- a/ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg2Cuvid.cs +++ b/ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg2Cuvid.cs @@ -2,6 +2,13 @@ public class DecoderMpeg2Cuvid : DecoderBase { + private readonly bool _contentIsInterlaced; + + public DecoderMpeg2Cuvid(bool contentIsInterlaced) + { + _contentIsInterlaced = contentIsInterlaced; + } + public override string Name => "mpeg2_cuvid"; public override IList InputOptions(InputFile inputFile) { @@ -13,5 +20,6 @@ public class DecoderMpeg2Cuvid : DecoderBase return result; } - protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware; + protected override FrameDataLocation OutputFrameDataLocation => + _contentIsInterlaced ? FrameDataLocation.Software : FrameDataLocation.Hardware; } diff --git a/ErsatzTV.FFmpeg/PipelineBuilder.cs b/ErsatzTV.FFmpeg/PipelineBuilder.cs index 9b6984a87..a86acf19a 100644 --- a/ErsatzTV.FFmpeg/PipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/PipelineBuilder.cs @@ -180,7 +180,7 @@ public class PipelineBuilder _pipelineSteps.Add(step); } - foreach (IDecoder decoder in AvailableDecoders.ForVideoFormat(ffmpegState, currentState, _logger)) + foreach (IDecoder decoder in AvailableDecoders.ForVideoFormat(ffmpegState, currentState, desiredState, _logger)) { foreach (VideoInputFile videoInputFile in _videoInputFile) { @@ -327,7 +327,8 @@ public class PipelineBuilder currentState = sarStep.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(sarStep)); } - else if (_watermarkInputFile.IsSome && currentState.PixelFormat != desiredState.PixelFormat) + + if (_watermarkInputFile.IsSome && currentState.PixelFormat != desiredState.PixelFormat) { // this should only happen with nvenc? // use scale filter to fix pixel format