Browse Source

ffmpeg lib hardware accel fixes (#663)

* more scaling fixes

* qsv fixes

* cleanup

* nvidia fixes

* vaapi fixes

* test nvidia by default
pull/664/head
Jason Dove 4 years ago committed by GitHub
parent
commit
19a7f90d52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 34
      ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs
  3. 13
      ErsatzTV.FFmpeg/Decoder/AvailableDecoders.cs
  4. 5
      ErsatzTV.FFmpeg/Decoder/DecoderVaapi.cs
  5. 16
      ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs
  6. 25
      ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderH264Nvenc.cs
  7. 25
      ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderHevcNvenc.cs
  8. 23
      ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderH264Vaapi.cs
  9. 25
      ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderHevcVaapi.cs
  10. 17
      ErsatzTV.FFmpeg/Filter/AvailableDeinterlaceFilters.cs
  11. 4
      ErsatzTV.FFmpeg/Filter/AvailableScaleFilters.cs
  12. 6
      ErsatzTV.FFmpeg/Filter/HardwareUploadFilter.cs
  13. 8
      ErsatzTV.FFmpeg/Filter/ScaleFilter.cs
  14. 8
      ErsatzTV.FFmpeg/Filter/Vaapi/ScaleVaapiFilter.cs
  15. 2
      ErsatzTV.FFmpeg/Option/HardwareAcceleration/VaapiHardwareAccelerationOption.cs
  16. 82
      ErsatzTV.FFmpeg/PipelineBuilder.cs

1
CHANGELOG.md

@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- 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
- Fix playback of all interlaced content with QSV acceleration
### Changed
- Framerate normalization will never normalize framerate below 24fps

34
ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs

@ -19,6 +19,7 @@ using LanguageExt; @@ -19,6 +19,7 @@ using LanguageExt;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
using Serilog;
using static LanguageExt.Prelude;
namespace ErsatzTV.Core.Tests.FFmpeg
@ -27,6 +28,18 @@ namespace ErsatzTV.Core.Tests.FFmpeg @@ -27,6 +28,18 @@ namespace ErsatzTV.Core.Tests.FFmpeg
[Explicit]
public class TranscodingTests
{
private static readonly ILoggerFactory LoggerFactory;
static TranscodingTests()
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.CreateLogger();
LoggerFactory = new LoggerFactory().AddSerilog(Log.Logger);
}
[Test]
[Explicit]
public void DeleteTestVideos()
@ -216,7 +229,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg @@ -216,7 +229,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
{
StartInfo = new ProcessStartInfo
{
FileName = "ffmpeg",
FileName = ExecutableName("ffmpeg"),
Arguments = args
}
};
@ -237,19 +250,19 @@ namespace ErsatzTV.Core.Tests.FFmpeg @@ -237,19 +250,19 @@ namespace ErsatzTV.Core.Tests.FFmpeg
It.Is<ArtworkKind>(x => x == ArtworkKind.Watermark),
It.IsAny<Option<int>>()))
.Returns(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "ErsatzTV.png"));
var oldService = new FFmpegProcessService(
new FFmpegPlaybackSettingsCalculator(),
new FakeStreamSelector(),
imageCache.Object,
new Mock<ITempFilePool>().Object,
new Mock<ILogger<FFmpegProcessService>>().Object);
LoggerFactory.CreateLogger<FFmpegProcessService>());
var service = new FFmpegLibraryProcessService(
oldService,
new FFmpegPlaybackSettingsCalculator(),
new FakeStreamSelector(),
new Mock<ILogger<FFmpegLibraryProcessService>>().Object);
LoggerFactory.CreateLogger<FFmpegLibraryProcessService>());
var v = new MediaVersion
{
@ -270,11 +283,11 @@ namespace ErsatzTV.Core.Tests.FFmpeg @@ -270,11 +283,11 @@ namespace ErsatzTV.Core.Tests.FFmpeg
var localStatisticsProvider = new LocalStatisticsProvider(
metadataRepository.Object,
new LocalFileSystem(new Mock<ILogger<LocalFileSystem>>().Object),
new Mock<ILogger<LocalStatisticsProvider>>().Object);
new LocalFileSystem(LoggerFactory.CreateLogger<LocalFileSystem>()),
LoggerFactory.CreateLogger<LocalStatisticsProvider>());
await localStatisticsProvider.RefreshStatistics(
"ffprobe",
ExecutableName("ffprobe"),
new Movie
{
MediaVersions = new List<MediaVersion>
@ -337,7 +350,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg @@ -337,7 +350,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
}
Process process = await service.ForPlayoutItem(
"ffmpeg",
ExecutableName("ffmpeg"),
false,
new Channel(Guid.NewGuid())
{
@ -442,7 +455,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg @@ -442,7 +455,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
}
using var sha = SHA256.Create();
byte[] textData = System.Text.Encoding.UTF8.GetBytes(text);
byte[] textData = Encoding.UTF8.GetBytes(text);
byte[] hash = sha.ComputeHash(textData);
return BitConverter.ToString(hash).Replace("-", string.Empty);
}
@ -455,5 +468,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg @@ -455,5 +468,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
public Task<Option<MediaStream>> SelectAudioStream(Channel channel, MediaVersion version) =>
Optional(version.Streams.First(s => s.MediaStreamKind == MediaStreamKind.Audio)).AsTask();
}
private static string ExecutableName(string baseName) =>
OperatingSystem.IsWindows() ? $"{baseName}.exe" : baseName;
}
}

13
ErsatzTV.FFmpeg/Decoder/AvailableDecoders.cs

@ -12,6 +12,7 @@ public static class AvailableDecoders @@ -12,6 +12,7 @@ public static class AvailableDecoders
FFmpegState ffmpegState,
FrameState currentState,
FrameState desiredState,
Option<WatermarkInputFile> watermarkInputFile,
ILogger logger)
{
return (ffmpegState.HardwareAccelerationMode, currentState.VideoFormat,
@ -41,14 +42,22 @@ public static class AvailableDecoders @@ -41,14 +42,22 @@ public static class AvailableDecoders
(HardwareAccelerationMode.Qsv, VideoFormat.H264, PixelFormat.YUV420P10LE or PixelFormat.YUV444P10LE) =>
new DecoderH264(),
// qsv uses software deinterlace filter, so decode in software
(HardwareAccelerationMode.Qsv, VideoFormat.H264, _) when desiredState.Deinterlaced => new DecoderH264(),
(HardwareAccelerationMode.Qsv, VideoFormat.Mpeg2Video, _) when desiredState.Deinterlaced =>
new DecoderMpeg2Video(),
(HardwareAccelerationMode.Qsv, VideoFormat.Hevc, _) => new DecoderHevcQsv(),
(HardwareAccelerationMode.Qsv, VideoFormat.H264, _) => new DecoderH264Qsv(),
(HardwareAccelerationMode.Qsv, VideoFormat.Mpeg2Video, _) => new DecoderMpeg2Qsv(),
(HardwareAccelerationMode.Qsv, VideoFormat.Vc1, _) => new DecoderVc1Qsv(),
(HardwareAccelerationMode.Qsv, VideoFormat.Vp9, _) => new DecoderVp9Qsv(),
// vaapi should use implicit decoders
(HardwareAccelerationMode.Vaapi, _, _) => new DecoderVaapi(),
// vaapi should use implicit decoders when scaling or no watermark
// otherwise, fall back to software decoders
(HardwareAccelerationMode.Vaapi, _, _) when watermarkInputFile.IsNone ||
(currentState.ScaledSize != desiredState.ScaledSize) =>
new DecoderVaapi(),
// videotoolbox should use implicit decoders
(HardwareAccelerationMode.VideoToolbox, _, _) => new DecoderVideoToolbox(),

5
ErsatzTV.FFmpeg/Decoder/DecoderVaapi.cs

@ -5,8 +5,11 @@ namespace ErsatzTV.FFmpeg.Decoder; @@ -5,8 +5,11 @@ namespace ErsatzTV.FFmpeg.Decoder;
public class DecoderVaapi : DecoderBase
{
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
public override string Name => "implicit_vaapi";
public override IList<string> InputOptions(InputFile inputFile) => Array.Empty<string>();
public override IList<string> InputOptions(InputFile inputFile) =>
new List<string> { "-hwaccel_output_format", "vaapi" };
public override FrameState NextState(FrameState currentState)
{

16
ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs

@ -19,16 +19,24 @@ public static class AvailableEncoders @@ -19,16 +19,24 @@ public static class AvailableEncoders
ILogger logger) =>
(ffmpegState.HardwareAccelerationMode, desiredState.VideoFormat) switch
{
(HardwareAccelerationMode.Nvenc, VideoFormat.Hevc) => new EncoderHevcNvenc(),
(HardwareAccelerationMode.Nvenc, VideoFormat.H264) => new EncoderH264Nvenc(),
(HardwareAccelerationMode.Nvenc, VideoFormat.Hevc) => new EncoderHevcNvenc(
currentState,
maybeWatermarkInputFile),
(HardwareAccelerationMode.Nvenc, VideoFormat.H264) => new EncoderH264Nvenc(
currentState,
maybeWatermarkInputFile),
(HardwareAccelerationMode.Qsv, VideoFormat.Hevc) => new EncoderHevcQsv(
currentState,
maybeWatermarkInputFile),
(HardwareAccelerationMode.Qsv, VideoFormat.H264) => new EncoderH264Qsv(currentState),
(HardwareAccelerationMode.Vaapi, VideoFormat.Hevc) => new EncoderHevcVaapi(currentState),
(HardwareAccelerationMode.Vaapi, VideoFormat.H264) => new EncoderH264Vaapi(currentState),
(HardwareAccelerationMode.Vaapi, VideoFormat.Hevc) => new EncoderHevcVaapi(
currentState,
maybeWatermarkInputFile),
(HardwareAccelerationMode.Vaapi, VideoFormat.H264) => new EncoderH264Vaapi(
currentState,
maybeWatermarkInputFile),
(HardwareAccelerationMode.VideoToolbox, VideoFormat.Hevc) => new EncoderHevcVideoToolbox(),
(HardwareAccelerationMode.VideoToolbox, VideoFormat.H264) => new EncoderH264VideoToolbox(),

25
ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderH264Nvenc.cs

@ -1,9 +1,19 @@ @@ -1,9 +1,19 @@
using ErsatzTV.FFmpeg.Format;
using LanguageExt;
namespace ErsatzTV.FFmpeg.Encoder.Nvenc;
public class EncoderH264Nvenc : EncoderBase
{
private readonly FrameState _currentState;
private readonly Option<WatermarkInputFile> _maybeWatermarkInputFile;
public EncoderH264Nvenc(FrameState currentState, Option<WatermarkInputFile> maybeWatermarkInputFile)
{
_currentState = currentState;
_maybeWatermarkInputFile = maybeWatermarkInputFile;
}
public override FrameState NextState(FrameState currentState) => currentState with
{
VideoFormat = VideoFormat.H264,
@ -12,4 +22,19 @@ public class EncoderH264Nvenc : EncoderBase @@ -12,4 +22,19 @@ public class EncoderH264Nvenc : EncoderBase
public override string Name => "h264_nvenc";
public override StreamKind Kind => StreamKind.Video;
// need to upload if we're still in software and a watermark is used
public override string Filter
{
get
{
// only upload to hw if we need to overlay a watermark
if (_maybeWatermarkInputFile.IsSome && _currentState.FrameDataLocation == FrameDataLocation.Software)
{
return "hwupload_cuda";
}
return string.Empty;
}
}
}

25
ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderHevcNvenc.cs

@ -1,9 +1,19 @@ @@ -1,9 +1,19 @@
using ErsatzTV.FFmpeg.Format;
using LanguageExt;
namespace ErsatzTV.FFmpeg.Encoder.Nvenc;
public class EncoderHevcNvenc : EncoderBase
{
private readonly FrameState _currentState;
private readonly Option<WatermarkInputFile> _maybeWatermarkInputFile;
public EncoderHevcNvenc(FrameState currentState, Option<WatermarkInputFile> maybeWatermarkInputFile)
{
_currentState = currentState;
_maybeWatermarkInputFile = maybeWatermarkInputFile;
}
public override FrameState NextState(FrameState currentState) => currentState with
{
VideoFormat = VideoFormat.Hevc,
@ -12,4 +22,19 @@ public class EncoderHevcNvenc : EncoderBase @@ -12,4 +22,19 @@ public class EncoderHevcNvenc : EncoderBase
public override string Name => "hevc_nvenc";
public override StreamKind Kind => StreamKind.Video;
// need to upload if we're still in software and a watermark is used
public override string Filter
{
get
{
// only upload to hw if we need to overlay a watermark
if (_maybeWatermarkInputFile.IsSome && _currentState.FrameDataLocation == FrameDataLocation.Software)
{
return "hwupload_cuda";
}
return string.Empty;
}
}
}

23
ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderH264Vaapi.cs

@ -1,26 +1,39 @@ @@ -1,26 +1,39 @@
using ErsatzTV.FFmpeg.Format;
using LanguageExt;
namespace ErsatzTV.FFmpeg.Encoder.Vaapi;
public class EncoderH264Vaapi : EncoderBase
{
private readonly FrameState _currentState;
private readonly Option<WatermarkInputFile> _maybeWatermarkInputFile;
public EncoderH264Vaapi(FrameState currentState)
public EncoderH264Vaapi(FrameState currentState, Option<WatermarkInputFile> maybeWatermarkInputFile)
{
_currentState = currentState;
_maybeWatermarkInputFile = maybeWatermarkInputFile;
}
public override FrameState NextState(FrameState currentState) => currentState with
{
VideoFormat = VideoFormat.H264,
FrameDataLocation = FrameDataLocation.Hardware
// don't change the frame data location
};
public override string Name => "h264_vaapi";
public override StreamKind Kind => StreamKind.Video;
public override string Filter => _currentState.FrameDataLocation == FrameDataLocation.Software
? "format=nv12|vaapi,hwupload"
: string.Empty;
// need to upload if we're still in software unless a watermark is used
public override string Filter
{
get
{
if (_maybeWatermarkInputFile.IsNone && _currentState.FrameDataLocation == FrameDataLocation.Software)
{
return "format=nv12|vaapi,hwupload";
}
return string.Empty;
}
}
}

25
ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderHevcVaapi.cs

@ -1,26 +1,39 @@ @@ -1,26 +1,39 @@
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Encoder.Vaapi;
using LanguageExt;
public class EncoderHevcVaapi : EncoderBase
{
private readonly FrameState _currentState;
private readonly Option<WatermarkInputFile> _maybeWatermarkInputFile;
public EncoderHevcVaapi(FrameState currentState)
public EncoderHevcVaapi(FrameState currentState, Option<WatermarkInputFile> maybeWatermarkInputFile)
{
_currentState = currentState;
_maybeWatermarkInputFile = maybeWatermarkInputFile;
}
public override FrameState NextState(FrameState currentState) => currentState with
{
VideoFormat = VideoFormat.Hevc,
FrameDataLocation = FrameDataLocation.Hardware
VideoFormat = VideoFormat.Hevc
// don't change the frame data location
};
public override string Name => "hevc_vaapi";
public override StreamKind Kind => StreamKind.Video;
public override string Filter => _currentState.FrameDataLocation == FrameDataLocation.Software
? "format=nv12|vaapi,hwupload"
: string.Empty;
// need to upload if we're still in software unless a watermark is used
public override string Filter
{
get
{
if (_maybeWatermarkInputFile.IsNone && _currentState.FrameDataLocation == FrameDataLocation.Software)
{
return "format=nv12|vaapi,hwupload";
}
return string.Empty;
}
}
}

17
ErsatzTV.FFmpeg/Filter/AvailableDeinterlaceFilters.cs

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
using ErsatzTV.FFmpeg.Filter.Cuda;
using ErsatzTV.FFmpeg.Filter.Qsv;
using ErsatzTV.FFmpeg.Filter.Vaapi;
using LanguageExt;
namespace ErsatzTV.FFmpeg.Filter;
@ -8,12 +8,21 @@ public static class AvailableDeinterlaceFilters @@ -8,12 +8,21 @@ public static class AvailableDeinterlaceFilters
{
public static IPipelineFilterStep ForAcceleration(
HardwareAccelerationMode accelMode,
FrameState currentState) =>
FrameState currentState,
FrameState desiredState,
Option<WatermarkInputFile> watermarkInputFile) =>
accelMode switch
{
HardwareAccelerationMode.Nvenc => new YadifCudaFilter(currentState),
HardwareAccelerationMode.Qsv => new DeinterlaceQsvFilter(currentState),
HardwareAccelerationMode.Vaapi => new DeinterlaceVaapiFilter(currentState),
// deinterlace_qsv seems to create timestamp issues
// HardwareAccelerationMode.Qsv => new DeinterlaceQsvFilter(currentState),
// fall back to software deinterlace with watermark and no scaling
HardwareAccelerationMode.Vaapi when watermarkInputFile.IsNone ||
(currentState.ScaledSize != desiredState.ScaledSize) =>
new DeinterlaceVaapiFilter(currentState),
_ => new YadifFilter(currentState)
};
}

4
ErsatzTV.FFmpeg/Filter/AvailableScaleFilters.cs

@ -14,7 +14,9 @@ public static class AvailableScaleFilters @@ -14,7 +14,9 @@ public static class AvailableScaleFilters
accelMode switch
{
HardwareAccelerationMode.Nvenc => new ScaleCudaFilter(currentState, scaledSize, paddedSize),
HardwareAccelerationMode.Qsv => new ScaleQsvFilter(currentState, scaledSize),
HardwareAccelerationMode.Qsv when currentState.FrameDataLocation == FrameDataLocation.Hardware ||
scaledSize == paddedSize =>
new ScaleQsvFilter(currentState, scaledSize),
HardwareAccelerationMode.Vaapi => new ScaleVaapiFilter(currentState, scaledSize, paddedSize),
_ => new ScaleFilter(currentState, scaledSize, paddedSize)
};

6
ErsatzTV.FFmpeg/Filter/HardwareUploadFilter.cs

@ -9,7 +9,11 @@ public class HardwareUploadFilter : BaseFilter @@ -9,7 +9,11 @@ public class HardwareUploadFilter : BaseFilter
_ffmpegState = ffmpegState;
}
public override FrameState NextState(FrameState currentState) => currentState;
public override FrameState NextState(FrameState currentState) => _ffmpegState.HardwareAccelerationMode switch
{
HardwareAccelerationMode.None => currentState,
_ => currentState with { FrameDataLocation = FrameDataLocation.Hardware }
};
public override string Filter => _ffmpegState.HardwareAccelerationMode switch
{

8
ErsatzTV.FFmpeg/Filter/ScaleFilter.cs

@ -19,8 +19,14 @@ public class ScaleFilter : BaseFilter @@ -19,8 +19,14 @@ public class ScaleFilter : BaseFilter
{
get
{
string aspectRatio = string.Empty;
if (_scaledSize != _paddedSize)
{
aspectRatio = ":force_original_aspect_ratio=decrease";
}
string scale =
$"scale={_paddedSize.Width}:{_paddedSize.Height}:flags=fast_bilinear:force_original_aspect_ratio=decrease";
$"scale={_paddedSize.Width}:{_paddedSize.Height}:flags=fast_bilinear{aspectRatio}";
string hwdownload = string.Empty;
if (_currentState.FrameDataLocation == FrameDataLocation.Hardware)

8
ErsatzTV.FFmpeg/Filter/Vaapi/ScaleVaapiFilter.cs

@ -37,8 +37,14 @@ public class ScaleVaapiFilter : BaseFilter @@ -37,8 +37,14 @@ public class ScaleVaapiFilter : BaseFilter
format = $":format={pixelFormat.FFmpegName}";
}
string aspectRatio = string.Empty;
if (_scaledSize != _paddedSize)
{
aspectRatio = ":force_original_aspect_ratio=1";
}
string targetSize = $"{_paddedSize.Width}:{_paddedSize.Height}";
scale = $"scale_vaapi={targetSize}:force_original_aspect_ratio=1:force_divisible_by=2{format}";
scale = $"scale_vaapi={targetSize}{aspectRatio}:force_divisible_by=2{format}";
}
if (_currentState.FrameDataLocation == FrameDataLocation.Hardware)

2
ErsatzTV.FFmpeg/Option/HardwareAcceleration/VaapiHardwareAccelerationOption.cs

@ -10,7 +10,7 @@ public class VaapiHardwareAccelerationOption : GlobalOption @@ -10,7 +10,7 @@ public class VaapiHardwareAccelerationOption : GlobalOption
}
public override IList<string> GlobalOptions => new List<string>
{ "-hwaccel", "vaapi", "-vaapi_device", _vaapiDevice, "-hwaccel_output_format", "vaapi" };
{ "-hwaccel", "vaapi", "-vaapi_device", _vaapiDevice };
public override FrameState NextState(FrameState currentState) => currentState with
{

82
ErsatzTV.FFmpeg/PipelineBuilder.cs

@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
using ErsatzTV.FFmpeg.Encoder;
using ErsatzTV.FFmpeg.Environment;
using ErsatzTV.FFmpeg.Filter;
using ErsatzTV.FFmpeg.Filter.Cuda;
using ErsatzTV.FFmpeg.Format;
using ErsatzTV.FFmpeg.Option;
using ErsatzTV.FFmpeg.Option.HardwareAcceleration;
@ -173,6 +174,14 @@ public class PipelineBuilder @@ -173,6 +174,14 @@ public class PipelineBuilder
desiredState = desiredState with { PixelFormat = new PixelFormatYuv420P() };
}
// qsv should stay nv12
if (ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Qsv &&
_watermarkInputFile.IsSome)
{
IPixelFormat pixelFormat = desiredState.PixelFormat.IfNone(new PixelFormatYuv420P());
desiredState = desiredState with { PixelFormat = new PixelFormatNv12(pixelFormat.Name) };
}
foreach (string desiredVaapiDriver in ffmpegState.VaapiDriver)
{
IPipelineStep step = new LibvaDriverNameVariable(desiredVaapiDriver);
@ -180,7 +189,12 @@ public class PipelineBuilder @@ -180,7 +189,12 @@ public class PipelineBuilder
_pipelineSteps.Add(step);
}
foreach (IDecoder decoder in AvailableDecoders.ForVideoFormat(ffmpegState, currentState, desiredState, _logger))
foreach (IDecoder decoder in AvailableDecoders.ForVideoFormat(
ffmpegState,
currentState,
desiredState,
_watermarkInputFile,
_logger))
{
foreach (VideoInputFile videoInputFile in _videoInputFile)
{
@ -256,7 +270,9 @@ public class PipelineBuilder @@ -256,7 +270,9 @@ public class PipelineBuilder
{
IPipelineFilterStep step = AvailableDeinterlaceFilters.ForAcceleration(
ffmpegState.HardwareAccelerationMode,
currentState);
currentState,
desiredState,
_watermarkInputFile);
currentState = step.NextState(currentState);
_videoInputFile.Iter(f => f.FilterSteps.Add(step));
}
@ -327,8 +343,9 @@ public class PipelineBuilder @@ -327,8 +343,9 @@ public class PipelineBuilder
currentState = sarStep.NextState(currentState);
_videoInputFile.Iter(f => f.FilterSteps.Add(sarStep));
}
if (_watermarkInputFile.IsSome && currentState.PixelFormat != desiredState.PixelFormat)
if (_watermarkInputFile.IsSome && currentState.PixelFormat.Map(pf => pf.FFmpegName) !=
desiredState.PixelFormat.Map(pf => pf.FFmpegName))
{
// this should only happen with nvenc?
// use scale filter to fix pixel format
@ -341,20 +358,59 @@ public class PipelineBuilder @@ -341,20 +358,59 @@ public class PipelineBuilder
currentState = formatFilter.NextState(currentState);
_videoInputFile.Iter(f => f.FilterSteps.Add(formatFilter));
_videoInputFile.Iter(f => f.FilterSteps.Add(new HardwareUploadFilter(ffmpegState)));
switch (ffmpegState.HardwareAccelerationMode)
{
case HardwareAccelerationMode.Nvenc:
var uploadFilter = new HardwareUploadFilter(ffmpegState);
currentState = uploadFilter.NextState(currentState);
_videoInputFile.Iter(f => f.FilterSteps.Add(uploadFilter));
break;
}
}
else
{
if (ffmpegState.HardwareAccelerationMode != HardwareAccelerationMode.Qsv)
{
// the filter re-applies the current pixel format, so we have to set it first
currentState = currentState with { PixelFormat = desiredState.PixelFormat };
IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration(
ffmpegState.HardwareAccelerationMode,
currentState,
desiredState.ScaledSize,
desiredState.PaddedSize);
currentState = scaleFilter.NextState(currentState);
_videoInputFile.Iter(f => f.FilterSteps.Add(scaleFilter));
}
}
}
}
// nvenc custom logic
if (ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Nvenc)
{
foreach (VideoInputFile videoInputFile in _videoInputFile)
{
// if we only deinterlace, we need to set pixel format again (using scale_cuda)
bool onlyYadif = videoInputFile.FilterSteps.Count == 1 &&
videoInputFile.FilterSteps.Any(fs => fs is YadifCudaFilter);
// if we have no filters and a watermark, we need to set pixel format
bool unfilteredWithWatermark = videoInputFile.FilterSteps.Count == 0
&& _watermarkInputFile.IsSome;
if (onlyYadif || unfilteredWithWatermark)
{
// the filter re-applies the current pixel format, so we have to set it first
currentState = currentState with { PixelFormat = desiredState.PixelFormat };
IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration(
ffmpegState.HardwareAccelerationMode,
currentState,
desiredState.ScaledSize,
desiredState.PaddedSize);
currentState = scaleFilter.NextState(currentState);
_videoInputFile.Iter(f => f.FilterSteps.Add(scaleFilter));
videoInputFile.FilterSteps.Add(scaleFilter);
}
}
}
@ -370,14 +426,18 @@ public class PipelineBuilder @@ -370,14 +426,18 @@ public class PipelineBuilder
_pipelineSteps.Add(step);
}
}
foreach (IPixelFormat desiredPixelFormat in desiredState.PixelFormat)
{
if (currentState.PixelFormat.Map(pf => pf.FFmpegName) != desiredPixelFormat.FFmpegName)
{
IPipelineStep step = new PixelFormatOutputOption(desiredPixelFormat);
currentState = step.NextState(currentState);
_pipelineSteps.Add(step);
// qsv doesn't seem to like this
if (ffmpegState.HardwareAccelerationMode != HardwareAccelerationMode.Qsv)
{
IPipelineStep step = new PixelFormatOutputOption(desiredPixelFormat);
currentState = step.NextState(currentState);
_pipelineSteps.Add(step);
}
}
}

Loading…
Cancel
Save