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/).
- Fix song sorting with `Chronological` and `Shuffle In Order` playback orders - Fix song sorting with `Chronological` and `Shuffle In Order` playback orders
- Fix watermark on scaled and/or padded video with NVIDIA acceleration - Fix watermark on scaled and/or padded video with NVIDIA acceleration
- Fix playback of interlaced mpeg2video content with NVIDIA acceleration - Fix playback of interlaced mpeg2video content with NVIDIA acceleration
- Fix playback of all interlaced content with QSV acceleration
### Changed ### Changed
- Framerate normalization will never normalize framerate below 24fps - Framerate normalization will never normalize framerate below 24fps

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

@ -19,6 +19,7 @@ using LanguageExt;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using Serilog;
using static LanguageExt.Prelude; using static LanguageExt.Prelude;
namespace ErsatzTV.Core.Tests.FFmpeg namespace ErsatzTV.Core.Tests.FFmpeg
@ -27,6 +28,18 @@ namespace ErsatzTV.Core.Tests.FFmpeg
[Explicit] [Explicit]
public class TranscodingTests 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] [Test]
[Explicit] [Explicit]
public void DeleteTestVideos() public void DeleteTestVideos()
@ -216,7 +229,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
{ {
StartInfo = new ProcessStartInfo StartInfo = new ProcessStartInfo
{ {
FileName = "ffmpeg", FileName = ExecutableName("ffmpeg"),
Arguments = args Arguments = args
} }
}; };
@ -237,19 +250,19 @@ namespace ErsatzTV.Core.Tests.FFmpeg
It.Is<ArtworkKind>(x => x == ArtworkKind.Watermark), It.Is<ArtworkKind>(x => x == ArtworkKind.Watermark),
It.IsAny<Option<int>>())) It.IsAny<Option<int>>()))
.Returns(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "ErsatzTV.png")); .Returns(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "ErsatzTV.png"));
var oldService = new FFmpegProcessService( var oldService = new FFmpegProcessService(
new FFmpegPlaybackSettingsCalculator(), new FFmpegPlaybackSettingsCalculator(),
new FakeStreamSelector(), new FakeStreamSelector(),
imageCache.Object, imageCache.Object,
new Mock<ITempFilePool>().Object, new Mock<ITempFilePool>().Object,
new Mock<ILogger<FFmpegProcessService>>().Object); LoggerFactory.CreateLogger<FFmpegProcessService>());
var service = new FFmpegLibraryProcessService( var service = new FFmpegLibraryProcessService(
oldService, oldService,
new FFmpegPlaybackSettingsCalculator(), new FFmpegPlaybackSettingsCalculator(),
new FakeStreamSelector(), new FakeStreamSelector(),
new Mock<ILogger<FFmpegLibraryProcessService>>().Object); LoggerFactory.CreateLogger<FFmpegLibraryProcessService>());
var v = new MediaVersion var v = new MediaVersion
{ {
@ -270,11 +283,11 @@ namespace ErsatzTV.Core.Tests.FFmpeg
var localStatisticsProvider = new LocalStatisticsProvider( var localStatisticsProvider = new LocalStatisticsProvider(
metadataRepository.Object, metadataRepository.Object,
new LocalFileSystem(new Mock<ILogger<LocalFileSystem>>().Object), new LocalFileSystem(LoggerFactory.CreateLogger<LocalFileSystem>()),
new Mock<ILogger<LocalStatisticsProvider>>().Object); LoggerFactory.CreateLogger<LocalStatisticsProvider>());
await localStatisticsProvider.RefreshStatistics( await localStatisticsProvider.RefreshStatistics(
"ffprobe", ExecutableName("ffprobe"),
new Movie new Movie
{ {
MediaVersions = new List<MediaVersion> MediaVersions = new List<MediaVersion>
@ -337,7 +350,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
} }
Process process = await service.ForPlayoutItem( Process process = await service.ForPlayoutItem(
"ffmpeg", ExecutableName("ffmpeg"),
false, false,
new Channel(Guid.NewGuid()) new Channel(Guid.NewGuid())
{ {
@ -442,7 +455,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
} }
using var sha = SHA256.Create(); using var sha = SHA256.Create();
byte[] textData = System.Text.Encoding.UTF8.GetBytes(text); byte[] textData = Encoding.UTF8.GetBytes(text);
byte[] hash = sha.ComputeHash(textData); byte[] hash = sha.ComputeHash(textData);
return BitConverter.ToString(hash).Replace("-", string.Empty); return BitConverter.ToString(hash).Replace("-", string.Empty);
} }
@ -455,5 +468,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
public Task<Option<MediaStream>> SelectAudioStream(Channel channel, MediaVersion version) => public Task<Option<MediaStream>> SelectAudioStream(Channel channel, MediaVersion version) =>
Optional(version.Streams.First(s => s.MediaStreamKind == MediaStreamKind.Audio)).AsTask(); 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
FFmpegState ffmpegState, FFmpegState ffmpegState,
FrameState currentState, FrameState currentState,
FrameState desiredState, FrameState desiredState,
Option<WatermarkInputFile> watermarkInputFile,
ILogger logger) ILogger logger)
{ {
return (ffmpegState.HardwareAccelerationMode, currentState.VideoFormat, return (ffmpegState.HardwareAccelerationMode, currentState.VideoFormat,
@ -41,14 +42,22 @@ public static class AvailableDecoders
(HardwareAccelerationMode.Qsv, VideoFormat.H264, PixelFormat.YUV420P10LE or PixelFormat.YUV444P10LE) => (HardwareAccelerationMode.Qsv, VideoFormat.H264, PixelFormat.YUV420P10LE or PixelFormat.YUV444P10LE) =>
new DecoderH264(), 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.Hevc, _) => new DecoderHevcQsv(),
(HardwareAccelerationMode.Qsv, VideoFormat.H264, _) => new DecoderH264Qsv(), (HardwareAccelerationMode.Qsv, VideoFormat.H264, _) => new DecoderH264Qsv(),
(HardwareAccelerationMode.Qsv, VideoFormat.Mpeg2Video, _) => new DecoderMpeg2Qsv(), (HardwareAccelerationMode.Qsv, VideoFormat.Mpeg2Video, _) => new DecoderMpeg2Qsv(),
(HardwareAccelerationMode.Qsv, VideoFormat.Vc1, _) => new DecoderVc1Qsv(), (HardwareAccelerationMode.Qsv, VideoFormat.Vc1, _) => new DecoderVc1Qsv(),
(HardwareAccelerationMode.Qsv, VideoFormat.Vp9, _) => new DecoderVp9Qsv(), (HardwareAccelerationMode.Qsv, VideoFormat.Vp9, _) => new DecoderVp9Qsv(),
// vaapi should use implicit decoders // vaapi should use implicit decoders when scaling or no watermark
(HardwareAccelerationMode.Vaapi, _, _) => new DecoderVaapi(), // otherwise, fall back to software decoders
(HardwareAccelerationMode.Vaapi, _, _) when watermarkInputFile.IsNone ||
(currentState.ScaledSize != desiredState.ScaledSize) =>
new DecoderVaapi(),
// videotoolbox should use implicit decoders // videotoolbox should use implicit decoders
(HardwareAccelerationMode.VideoToolbox, _, _) => new DecoderVideoToolbox(), (HardwareAccelerationMode.VideoToolbox, _, _) => new DecoderVideoToolbox(),

5
ErsatzTV.FFmpeg/Decoder/DecoderVaapi.cs

@ -5,8 +5,11 @@ namespace ErsatzTV.FFmpeg.Decoder;
public class DecoderVaapi : DecoderBase public class DecoderVaapi : DecoderBase
{ {
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software; protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
public override string Name => "implicit_vaapi"; 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) public override FrameState NextState(FrameState currentState)
{ {

16
ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs

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

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

@ -1,9 +1,19 @@
using ErsatzTV.FFmpeg.Format; using ErsatzTV.FFmpeg.Format;
using LanguageExt;
namespace ErsatzTV.FFmpeg.Encoder.Nvenc; namespace ErsatzTV.FFmpeg.Encoder.Nvenc;
public class EncoderH264Nvenc : EncoderBase 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 public override FrameState NextState(FrameState currentState) => currentState with
{ {
VideoFormat = VideoFormat.H264, VideoFormat = VideoFormat.H264,
@ -12,4 +22,19 @@ public class EncoderH264Nvenc : EncoderBase
public override string Name => "h264_nvenc"; public override string Name => "h264_nvenc";
public override StreamKind Kind => StreamKind.Video; 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 @@
using ErsatzTV.FFmpeg.Format; using ErsatzTV.FFmpeg.Format;
using LanguageExt;
namespace ErsatzTV.FFmpeg.Encoder.Nvenc; namespace ErsatzTV.FFmpeg.Encoder.Nvenc;
public class EncoderHevcNvenc : EncoderBase 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 public override FrameState NextState(FrameState currentState) => currentState with
{ {
VideoFormat = VideoFormat.Hevc, VideoFormat = VideoFormat.Hevc,
@ -12,4 +22,19 @@ public class EncoderHevcNvenc : EncoderBase
public override string Name => "hevc_nvenc"; public override string Name => "hevc_nvenc";
public override StreamKind Kind => StreamKind.Video; 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 @@
using ErsatzTV.FFmpeg.Format; using ErsatzTV.FFmpeg.Format;
using LanguageExt;
namespace ErsatzTV.FFmpeg.Encoder.Vaapi; namespace ErsatzTV.FFmpeg.Encoder.Vaapi;
public class EncoderH264Vaapi : EncoderBase public class EncoderH264Vaapi : EncoderBase
{ {
private readonly FrameState _currentState; private readonly FrameState _currentState;
private readonly Option<WatermarkInputFile> _maybeWatermarkInputFile;
public EncoderH264Vaapi(FrameState currentState) public EncoderH264Vaapi(FrameState currentState, Option<WatermarkInputFile> maybeWatermarkInputFile)
{ {
_currentState = currentState; _currentState = currentState;
_maybeWatermarkInputFile = maybeWatermarkInputFile;
} }
public override FrameState NextState(FrameState currentState) => currentState with public override FrameState NextState(FrameState currentState) => currentState with
{ {
VideoFormat = VideoFormat.H264, VideoFormat = VideoFormat.H264,
FrameDataLocation = FrameDataLocation.Hardware // don't change the frame data location
}; };
public override string Name => "h264_vaapi"; public override string Name => "h264_vaapi";
public override StreamKind Kind => StreamKind.Video; public override StreamKind Kind => StreamKind.Video;
public override string Filter => _currentState.FrameDataLocation == FrameDataLocation.Software // need to upload if we're still in software unless a watermark is used
? "format=nv12|vaapi,hwupload" public override string Filter
: string.Empty; {
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 @@
using ErsatzTV.FFmpeg.Format; using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Encoder.Vaapi; namespace ErsatzTV.FFmpeg.Encoder.Vaapi;
using LanguageExt;
public class EncoderHevcVaapi : EncoderBase public class EncoderHevcVaapi : EncoderBase
{ {
private readonly FrameState _currentState; private readonly FrameState _currentState;
private readonly Option<WatermarkInputFile> _maybeWatermarkInputFile;
public EncoderHevcVaapi(FrameState currentState) public EncoderHevcVaapi(FrameState currentState, Option<WatermarkInputFile> maybeWatermarkInputFile)
{ {
_currentState = currentState; _currentState = currentState;
_maybeWatermarkInputFile = maybeWatermarkInputFile;
} }
public override FrameState NextState(FrameState currentState) => currentState with public override FrameState NextState(FrameState currentState) => currentState with
{ {
VideoFormat = VideoFormat.Hevc, VideoFormat = VideoFormat.Hevc
FrameDataLocation = FrameDataLocation.Hardware // don't change the frame data location
}; };
public override string Name => "hevc_vaapi"; public override string Name => "hevc_vaapi";
public override StreamKind Kind => StreamKind.Video; public override StreamKind Kind => StreamKind.Video;
public override string Filter => _currentState.FrameDataLocation == FrameDataLocation.Software // need to upload if we're still in software unless a watermark is used
? "format=nv12|vaapi,hwupload" public override string Filter
: string.Empty; {
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 @@
using ErsatzTV.FFmpeg.Filter.Cuda; using ErsatzTV.FFmpeg.Filter.Cuda;
using ErsatzTV.FFmpeg.Filter.Qsv;
using ErsatzTV.FFmpeg.Filter.Vaapi; using ErsatzTV.FFmpeg.Filter.Vaapi;
using LanguageExt;
namespace ErsatzTV.FFmpeg.Filter; namespace ErsatzTV.FFmpeg.Filter;
@ -8,12 +8,21 @@ public static class AvailableDeinterlaceFilters
{ {
public static IPipelineFilterStep ForAcceleration( public static IPipelineFilterStep ForAcceleration(
HardwareAccelerationMode accelMode, HardwareAccelerationMode accelMode,
FrameState currentState) => FrameState currentState,
FrameState desiredState,
Option<WatermarkInputFile> watermarkInputFile) =>
accelMode switch accelMode switch
{ {
HardwareAccelerationMode.Nvenc => new YadifCudaFilter(currentState), 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) _ => new YadifFilter(currentState)
}; };
} }

4
ErsatzTV.FFmpeg/Filter/AvailableScaleFilters.cs

@ -14,7 +14,9 @@ public static class AvailableScaleFilters
accelMode switch accelMode switch
{ {
HardwareAccelerationMode.Nvenc => new ScaleCudaFilter(currentState, scaledSize, paddedSize), 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), HardwareAccelerationMode.Vaapi => new ScaleVaapiFilter(currentState, scaledSize, paddedSize),
_ => new ScaleFilter(currentState, scaledSize, paddedSize) _ => new ScaleFilter(currentState, scaledSize, paddedSize)
}; };

6
ErsatzTV.FFmpeg/Filter/HardwareUploadFilter.cs

@ -9,7 +9,11 @@ public class HardwareUploadFilter : BaseFilter
_ffmpegState = ffmpegState; _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 public override string Filter => _ffmpegState.HardwareAccelerationMode switch
{ {

8
ErsatzTV.FFmpeg/Filter/ScaleFilter.cs

@ -19,8 +19,14 @@ public class ScaleFilter : BaseFilter
{ {
get get
{ {
string aspectRatio = string.Empty;
if (_scaledSize != _paddedSize)
{
aspectRatio = ":force_original_aspect_ratio=decrease";
}
string scale = 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; string hwdownload = string.Empty;
if (_currentState.FrameDataLocation == FrameDataLocation.Hardware) if (_currentState.FrameDataLocation == FrameDataLocation.Hardware)

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

@ -37,8 +37,14 @@ public class ScaleVaapiFilter : BaseFilter
format = $":format={pixelFormat.FFmpegName}"; format = $":format={pixelFormat.FFmpegName}";
} }
string aspectRatio = string.Empty;
if (_scaledSize != _paddedSize)
{
aspectRatio = ":force_original_aspect_ratio=1";
}
string targetSize = $"{_paddedSize.Width}:{_paddedSize.Height}"; 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) if (_currentState.FrameDataLocation == FrameDataLocation.Hardware)

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

@ -10,7 +10,7 @@ public class VaapiHardwareAccelerationOption : GlobalOption
} }
public override IList<string> GlobalOptions => new List<string> 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 public override FrameState NextState(FrameState currentState) => currentState with
{ {

82
ErsatzTV.FFmpeg/PipelineBuilder.cs

@ -2,6 +2,7 @@
using ErsatzTV.FFmpeg.Encoder; using ErsatzTV.FFmpeg.Encoder;
using ErsatzTV.FFmpeg.Environment; using ErsatzTV.FFmpeg.Environment;
using ErsatzTV.FFmpeg.Filter; using ErsatzTV.FFmpeg.Filter;
using ErsatzTV.FFmpeg.Filter.Cuda;
using ErsatzTV.FFmpeg.Format; using ErsatzTV.FFmpeg.Format;
using ErsatzTV.FFmpeg.Option; using ErsatzTV.FFmpeg.Option;
using ErsatzTV.FFmpeg.Option.HardwareAcceleration; using ErsatzTV.FFmpeg.Option.HardwareAcceleration;
@ -173,6 +174,14 @@ public class PipelineBuilder
desiredState = desiredState with { PixelFormat = new PixelFormatYuv420P() }; 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) foreach (string desiredVaapiDriver in ffmpegState.VaapiDriver)
{ {
IPipelineStep step = new LibvaDriverNameVariable(desiredVaapiDriver); IPipelineStep step = new LibvaDriverNameVariable(desiredVaapiDriver);
@ -180,7 +189,12 @@ public class PipelineBuilder
_pipelineSteps.Add(step); _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) foreach (VideoInputFile videoInputFile in _videoInputFile)
{ {
@ -256,7 +270,9 @@ public class PipelineBuilder
{ {
IPipelineFilterStep step = AvailableDeinterlaceFilters.ForAcceleration( IPipelineFilterStep step = AvailableDeinterlaceFilters.ForAcceleration(
ffmpegState.HardwareAccelerationMode, ffmpegState.HardwareAccelerationMode,
currentState); currentState,
desiredState,
_watermarkInputFile);
currentState = step.NextState(currentState); currentState = step.NextState(currentState);
_videoInputFile.Iter(f => f.FilterSteps.Add(step)); _videoInputFile.Iter(f => f.FilterSteps.Add(step));
} }
@ -327,8 +343,9 @@ public class PipelineBuilder
currentState = sarStep.NextState(currentState); currentState = sarStep.NextState(currentState);
_videoInputFile.Iter(f => f.FilterSteps.Add(sarStep)); _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? // this should only happen with nvenc?
// use scale filter to fix pixel format // use scale filter to fix pixel format
@ -341,20 +358,59 @@ public class PipelineBuilder
currentState = formatFilter.NextState(currentState); currentState = formatFilter.NextState(currentState);
_videoInputFile.Iter(f => f.FilterSteps.Add(formatFilter)); _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 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 // the filter re-applies the current pixel format, so we have to set it first
currentState = currentState with { PixelFormat = desiredState.PixelFormat }; currentState = currentState with { PixelFormat = desiredState.PixelFormat };
IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration( IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration(
ffmpegState.HardwareAccelerationMode, ffmpegState.HardwareAccelerationMode,
currentState, currentState,
desiredState.ScaledSize, desiredState.ScaledSize,
desiredState.PaddedSize); desiredState.PaddedSize);
currentState = scaleFilter.NextState(currentState); currentState = scaleFilter.NextState(currentState);
_videoInputFile.Iter(f => f.FilterSteps.Add(scaleFilter)); videoInputFile.FilterSteps.Add(scaleFilter);
} }
} }
} }
@ -370,14 +426,18 @@ public class PipelineBuilder
_pipelineSteps.Add(step); _pipelineSteps.Add(step);
} }
} }
foreach (IPixelFormat desiredPixelFormat in desiredState.PixelFormat) foreach (IPixelFormat desiredPixelFormat in desiredState.PixelFormat)
{ {
if (currentState.PixelFormat.Map(pf => pf.FFmpegName) != desiredPixelFormat.FFmpegName) if (currentState.PixelFormat.Map(pf => pf.FFmpegName) != desiredPixelFormat.FFmpegName)
{ {
IPipelineStep step = new PixelFormatOutputOption(desiredPixelFormat); // qsv doesn't seem to like this
currentState = step.NextState(currentState); if (ffmpegState.HardwareAccelerationMode != HardwareAccelerationMode.Qsv)
_pipelineSteps.Add(step); {
IPipelineStep step = new PixelFormatOutputOption(desiredPixelFormat);
currentState = step.NextState(currentState);
_pipelineSteps.Add(step);
}
} }
} }

Loading…
Cancel
Save