Browse Source

optimize ffmpeg capability cache (#1486)

* minimize cached ffmpeg capabilities

* use set intersect

* try disabling work ahead on nvidia/windows
pull/1489/head
Jason Dove 2 years ago committed by GitHub
parent
commit
14a88bd225
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 29
      ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs
  2. 31
      ErsatzTV.FFmpeg/Capabilities/FFmpegKnownDecoder.cs
  3. 13
      ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs
  4. 19
      ErsatzTV.FFmpeg/Capabilities/FFmpegKnownFilter.cs
  5. 27
      ErsatzTV.FFmpeg/Capabilities/FFmpegKnownHardwareAcceleration.cs
  6. 23
      ErsatzTV.FFmpeg/Capabilities/FFmpegKnownOption.cs
  7. 19
      ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs
  8. 8
      ErsatzTV.FFmpeg/Capabilities/IFFmpegCapabilities.cs
  9. 20
      ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs
  10. 4
      ErsatzTV.FFmpeg/InputOption/ReadrateInputOption.cs
  11. 12
      ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs
  12. 14
      ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs
  13. 1
      ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs
  14. 2
      ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs

29
ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs

@ -27,26 +27,31 @@ public class FFmpegCapabilities : IFFmpegCapabilities @@ -27,26 +27,31 @@ public class FFmpegCapabilities : IFFmpegCapabilities
public bool HasHardwareAcceleration(HardwareAccelerationMode hardwareAccelerationMode)
{
string accelToCheck = hardwareAccelerationMode switch
Option<FFmpegKnownHardwareAcceleration> maybeAccelToCheck = hardwareAccelerationMode switch
{
HardwareAccelerationMode.Amf => "amf",
HardwareAccelerationMode.Nvenc => "cuda",
HardwareAccelerationMode.Qsv => "qsv",
HardwareAccelerationMode.Vaapi => "vaapi",
HardwareAccelerationMode.VideoToolbox => "videotoolbox",
_ => string.Empty
HardwareAccelerationMode.Amf => FFmpegKnownHardwareAcceleration.Amf,
HardwareAccelerationMode.Nvenc => FFmpegKnownHardwareAcceleration.Cuda,
HardwareAccelerationMode.Qsv => FFmpegKnownHardwareAcceleration.Qsv,
HardwareAccelerationMode.Vaapi => FFmpegKnownHardwareAcceleration.Vaapi,
HardwareAccelerationMode.VideoToolbox => FFmpegKnownHardwareAcceleration.VideoToolbox,
_ => Option<FFmpegKnownHardwareAcceleration>.None
};
return !string.IsNullOrWhiteSpace(accelToCheck) && _ffmpegHardwareAccelerations.Contains(accelToCheck);
foreach (FFmpegKnownHardwareAcceleration accelToCheck in maybeAccelToCheck)
{
return _ffmpegHardwareAccelerations.Contains(accelToCheck.Name);
}
return false;
}
public bool HasDecoder(string decoder) => _ffmpegDecoders.Contains(decoder);
public bool HasDecoder(FFmpegKnownDecoder decoder) => _ffmpegDecoders.Contains(decoder.Name);
public bool HasEncoder(string encoder) => _ffmpegEncoders.Contains(encoder);
public bool HasEncoder(FFmpegKnownEncoder encoder) => _ffmpegEncoders.Contains(encoder.Name);
public bool HasFilter(string filter) => _ffmpegFilters.Contains(filter);
public bool HasFilter(FFmpegKnownFilter filter) => _ffmpegFilters.Contains(filter.Name);
public bool HasOption(string ffmpegOption) => _ffmpegOptions.Contains(ffmpegOption);
public bool HasOption(FFmpegKnownOption ffmpegOption) => _ffmpegOptions.Contains(ffmpegOption.Name);
public Option<IDecoder> SoftwareDecoderForVideoFormat(string videoFormat) =>
videoFormat switch

31
ErsatzTV.FFmpeg/Capabilities/FFmpegKnownDecoder.cs

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
namespace ErsatzTV.FFmpeg.Capabilities;
public record FFmpegKnownDecoder
{
public string Name { get; }
private FFmpegKnownDecoder(string Name)
{
this.Name = Name;
}
public static readonly FFmpegKnownDecoder Av1Cuvid = new("av1_cuvid");
public static readonly FFmpegKnownDecoder H264Cuvid = new("h264_cuvid");
public static readonly FFmpegKnownDecoder HevcCuvid = new("hevc_cuvid");
public static readonly FFmpegKnownDecoder Mpeg2Cuvid = new("mpeg2_cuvid");
public static readonly FFmpegKnownDecoder Mpeg4Cuvid = new("mpeg4_cuvid");
public static readonly FFmpegKnownDecoder Vc1Cuvid = new("vc1_cuvid");
public static readonly FFmpegKnownDecoder Vp9Cuvid = new("vp9_cuvid");
public static IList<string> AllDecoders =>
new[]
{
Av1Cuvid.Name,
H264Cuvid.Name,
HevcCuvid.Name,
Mpeg2Cuvid.Name,
Mpeg4Cuvid.Name,
Vc1Cuvid.Name,
Vp9Cuvid.Name
};
}

13
ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
namespace ErsatzTV.FFmpeg.Capabilities;
public record FFmpegKnownEncoder
{
public string Name { get; }
private FFmpegKnownEncoder(string Name)
{
this.Name = Name;
}
public static IList<string> AllEncoders => Array.Empty<string>();
}

19
ErsatzTV.FFmpeg/Capabilities/FFmpegKnownFilter.cs

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
namespace ErsatzTV.FFmpeg.Capabilities;
public record FFmpegKnownFilter
{
public string Name { get; }
private FFmpegKnownFilter(string Name)
{
this.Name = Name;
}
public static readonly FFmpegKnownFilter ScaleNpp = new("scale_npp");
public static IList<string> AllFilters =>
new[]
{
ScaleNpp.Name
};
}

27
ErsatzTV.FFmpeg/Capabilities/FFmpegKnownHardwareAcceleration.cs

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
namespace ErsatzTV.FFmpeg.Capabilities;
public record FFmpegKnownHardwareAcceleration
{
public string Name { get; }
private FFmpegKnownHardwareAcceleration(string Name)
{
this.Name = Name;
}
public static readonly FFmpegKnownHardwareAcceleration Amf = new("amf");
public static readonly FFmpegKnownHardwareAcceleration Cuda = new("cuda");
public static readonly FFmpegKnownHardwareAcceleration Qsv = new("qsv");
public static readonly FFmpegKnownHardwareAcceleration Vaapi = new("vaapi");
public static readonly FFmpegKnownHardwareAcceleration VideoToolbox = new("videotoolbox");
public static IList<string> AllAccels =>
new[]
{
Amf.Name,
Cuda.Name,
Qsv.Name,
Vaapi.Name,
VideoToolbox.Name
};
}

23
ErsatzTV.FFmpeg/Capabilities/FFmpegKnownOption.cs

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
using System.Diagnostics.CodeAnalysis;
namespace ErsatzTV.FFmpeg.Capabilities;
[SuppressMessage("ReSharper", "IdentifierTypo")]
[SuppressMessage("ReSharper", "StringLiteralTypo")]
public record FFmpegKnownOption
{
public string Name { get; }
private FFmpegKnownOption(string Name)
{
this.Name = Name;
}
public static readonly FFmpegKnownOption ReadrateInitialBurst = new("readrate_initial_burst");
public static IList<string> AllOptions =>
new[]
{
ReadrateInitialBurst.Name
};
}

19
ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs

@ -42,11 +42,20 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory @@ -42,11 +42,20 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
// TODO: validate amf somehow
IReadOnlySet<string> ffmpegHardwareAccelerations =
await GetFFmpegCapabilities(ffmpegPath, "hwaccels", ParseFFmpegAccelLine);
IReadOnlySet<string> ffmpegDecoders = await GetFFmpegCapabilities(ffmpegPath, "decoders", ParseFFmpegLine);
IReadOnlySet<string> ffmpegFilters = await GetFFmpegCapabilities(ffmpegPath, "filters", ParseFFmpegLine);
IReadOnlySet<string> ffmpegEncoders = await GetFFmpegCapabilities(ffmpegPath, "encoders", ParseFFmpegLine);
IReadOnlySet<string> ffmpegOptions = await GetFFmpegOptions(ffmpegPath);
await GetFFmpegCapabilities(ffmpegPath, "hwaccels", ParseFFmpegAccelLine)
.Map(set => set.Intersect(FFmpegKnownHardwareAcceleration.AllAccels).ToImmutableHashSet());
IReadOnlySet<string> ffmpegDecoders = await GetFFmpegCapabilities(ffmpegPath, "decoders", ParseFFmpegLine)
.Map(set => set.Intersect(FFmpegKnownDecoder.AllDecoders).ToImmutableHashSet());
IReadOnlySet<string> ffmpegFilters = await GetFFmpegCapabilities(ffmpegPath, "filters", ParseFFmpegLine)
.Map(set => set.Intersect(FFmpegKnownFilter.AllFilters).ToImmutableHashSet());
IReadOnlySet<string> ffmpegEncoders = await GetFFmpegCapabilities(ffmpegPath, "encoders", ParseFFmpegLine)
.Map(set => set.Intersect(FFmpegKnownEncoder.AllEncoders).ToImmutableHashSet());
IReadOnlySet<string> ffmpegOptions = await GetFFmpegOptions(ffmpegPath)
.Map(set => set.Intersect(FFmpegKnownOption.AllOptions).ToImmutableHashSet());
return new FFmpegCapabilities(
ffmpegHardwareAccelerations,

8
ErsatzTV.FFmpeg/Capabilities/IFFmpegCapabilities.cs

@ -5,9 +5,9 @@ namespace ErsatzTV.FFmpeg.Capabilities; @@ -5,9 +5,9 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public interface IFFmpegCapabilities
{
bool HasHardwareAcceleration(HardwareAccelerationMode hardwareAccelerationMode);
bool HasDecoder(string decoder);
bool HasEncoder(string encoder);
bool HasFilter(string filter);
bool HasOption(string ffmpegOption);
bool HasDecoder(FFmpegKnownDecoder decoder);
bool HasEncoder(FFmpegKnownEncoder encoder);
bool HasFilter(FFmpegKnownFilter filter);
bool HasOption(FFmpegKnownOption ffmpegOption);
Option<IDecoder> SoftwareDecoderForVideoFormat(string videoFormat);
}

20
ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs

@ -67,13 +67,15 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities @@ -67,13 +67,15 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities
{
return videoFormat switch
{
VideoFormat.Mpeg2Video => CheckHardwareCodec("mpeg2_cuvid", _ffmpegCapabilities.HasDecoder),
VideoFormat.Mpeg4 => CheckHardwareCodec("mpeg4_cuvid", _ffmpegCapabilities.HasDecoder),
VideoFormat.Vc1 => CheckHardwareCodec("vc1_cuvid", _ffmpegCapabilities.HasDecoder),
VideoFormat.H264 => CheckHardwareCodec("h264_cuvid", _ffmpegCapabilities.HasDecoder),
VideoFormat.Hevc => CheckHardwareCodec("hevc_cuvid", _ffmpegCapabilities.HasDecoder),
VideoFormat.Vp9 => CheckHardwareCodec("hevc_cuvid", _ffmpegCapabilities.HasDecoder),
VideoFormat.Av1 => CheckHardwareCodec("av1_cuvid", _ffmpegCapabilities.HasDecoder),
VideoFormat.Mpeg2Video => CheckHardwareCodec(
FFmpegKnownDecoder.Mpeg2Cuvid,
_ffmpegCapabilities.HasDecoder),
VideoFormat.Mpeg4 => CheckHardwareCodec(FFmpegKnownDecoder.Mpeg4Cuvid, _ffmpegCapabilities.HasDecoder),
VideoFormat.Vc1 => CheckHardwareCodec(FFmpegKnownDecoder.Vc1Cuvid, _ffmpegCapabilities.HasDecoder),
VideoFormat.H264 => CheckHardwareCodec(FFmpegKnownDecoder.H264Cuvid, _ffmpegCapabilities.HasDecoder),
VideoFormat.Hevc => CheckHardwareCodec(FFmpegKnownDecoder.HevcCuvid, _ffmpegCapabilities.HasDecoder),
VideoFormat.Vp9 => CheckHardwareCodec(FFmpegKnownDecoder.Vp9Cuvid, _ffmpegCapabilities.HasDecoder),
VideoFormat.Av1 => CheckHardwareCodec(FFmpegKnownDecoder.Av1Cuvid, _ffmpegCapabilities.HasDecoder),
_ => FFmpegCapability.Software
};
}
@ -108,14 +110,14 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities @@ -108,14 +110,14 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities
public Option<RateControlMode> GetRateControlMode(string videoFormat, Option<IPixelFormat> maybePixelFormat) =>
Option<RateControlMode>.None;
private FFmpegCapability CheckHardwareCodec(string codec, Func<string, bool> check)
private FFmpegCapability CheckHardwareCodec(FFmpegKnownDecoder codec, Func<FFmpegKnownDecoder, bool> check)
{
if (check(codec))
{
return FFmpegCapability.Hardware;
}
_logger.LogWarning("FFmpeg does not contain codec {Codec}; will fall back to software codec", codec);
_logger.LogWarning("FFmpeg does not contain codec {Codec}; will fall back to software codec", codec.Name);
return FFmpegCapability.Software;
}
}

4
ErsatzTV.FFmpeg/InputOption/ReadrateInputOption.cs

@ -31,11 +31,11 @@ public class ReadrateInputOption : IInputOption @@ -31,11 +31,11 @@ public class ReadrateInputOption : IInputOption
if (_initialBurstSeconds > 0)
{
if (!_ffmpegCapabilities.HasOption("readrate_initial_burst"))
if (!_ffmpegCapabilities.HasOption(FFmpegKnownOption.ReadrateInitialBurst))
{
_logger.LogWarning(
"FFmpeg is missing {Option} option; unable to transcode faster than realtime",
"readrate_initial_burst");
FFmpegKnownOption.ReadrateInitialBurst.Name);
return result;
}

12
ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
using System.Runtime.InteropServices;
using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.FFmpeg.Decoder;
using ErsatzTV.FFmpeg.Decoder.Cuvid;
@ -9,6 +10,7 @@ using ErsatzTV.FFmpeg.Format; @@ -9,6 +10,7 @@ using ErsatzTV.FFmpeg.Format;
using ErsatzTV.FFmpeg.GlobalOption.HardwareAcceleration;
using ErsatzTV.FFmpeg.InputOption;
using ErsatzTV.FFmpeg.OutputOption;
using ErsatzTV.FFmpeg.Runtime;
using ErsatzTV.FFmpeg.State;
using Microsoft.Extensions.Logging;
@ -16,11 +18,13 @@ namespace ErsatzTV.FFmpeg.Pipeline; @@ -16,11 +18,13 @@ namespace ErsatzTV.FFmpeg.Pipeline;
public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
{
private readonly IRuntimeInfo _runtimeInfo;
private readonly IFFmpegCapabilities _ffmpegCapabilities;
private readonly IHardwareCapabilities _hardwareCapabilities;
private readonly ILogger _logger;
public NvidiaPipelineBuilder(
IRuntimeInfo runtimeInfo,
IFFmpegCapabilities ffmpegCapabilities,
IHardwareCapabilities hardwareCapabilities,
HardwareAccelerationMode hardwareAccelerationMode,
@ -41,11 +45,17 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder @@ -41,11 +45,17 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
fontsFolder,
logger)
{
_runtimeInfo = runtimeInfo;
_ffmpegCapabilities = ffmpegCapabilities;
_hardwareCapabilities = hardwareCapabilities;
_logger = logger;
}
protected override bool IsNvidiaOnWindows(FFmpegState ffmpegState) =>
_runtimeInfo.IsOSPlatform(OSPlatform.Windows) &&
(ffmpegState.EncoderHardwareAccelerationMode is HardwareAccelerationMode.Nvenc ||
ffmpegState.DecoderHardwareAccelerationMode is HardwareAccelerationMode.Nvenc);
protected override FFmpegState SetAccelState(
VideoStream videoStream,
FFmpegState ffmpegState,
@ -468,7 +478,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder @@ -468,7 +478,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
if (currentState.PixelFormat.Map(pf => pf.BitDepth).IfNone(8) == 8)
{
if (_ffmpegCapabilities.HasFilter("scale_npp"))
if (_ffmpegCapabilities.HasFilter(FFmpegKnownFilter.ScaleNpp))
{
var subtitleHardwareUpload = new HardwareUploadCudaFilter(
currentState with { FrameDataLocation = FrameDataLocation.Software });

14
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.FFmpeg.Decoder;
using ErsatzTV.FFmpeg.Encoder;
@ -11,6 +12,7 @@ using ErsatzTV.FFmpeg.OutputFormat; @@ -11,6 +12,7 @@ using ErsatzTV.FFmpeg.OutputFormat;
using ErsatzTV.FFmpeg.OutputOption;
using ErsatzTV.FFmpeg.OutputOption.Metadata;
using ErsatzTV.FFmpeg.Protocol;
using ErsatzTV.FFmpeg.Runtime;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.FFmpeg.Pipeline;
@ -402,6 +404,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -402,6 +404,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
}
protected abstract bool IsIntelVaapiOrQsv(FFmpegState ffmpegState);
protected abstract bool IsNvidiaOnWindows(FFmpegState ffmpegState);
protected abstract FFmpegState SetAccelState(
VideoStream videoStream,
@ -441,7 +444,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -441,7 +444,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
: SetDecoder(videoInputFile, videoStream, ffmpegState, context);
SetStillImageInfiniteLoop(videoInputFile, videoStream, ffmpegState);
SetRealtimeInput(videoInputFile, desiredState);
SetRealtimeInput(videoInputFile, ffmpegState, desiredState);
SetInfiniteLoop(videoInputFile, videoStream, ffmpegState, desiredState);
SetFrameRateOutput(desiredState, pipelineSteps);
SetVideoTrackTimescaleOutput(desiredState, pipelineSteps);
@ -619,7 +622,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -619,7 +622,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
}
}
private void SetRealtimeInput(VideoInputFile videoInputFile, FrameState desiredState)
private void SetRealtimeInput(VideoInputFile videoInputFile, FFmpegState ffmpegState, FrameState desiredState)
{
int initialBurst;
if (!desiredState.Realtime)
@ -640,6 +643,13 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -640,6 +643,13 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
};
}
// TODO: when ffmpeg/nvenc stops being weird on windows, remove this workaround
if (IsNvidiaOnWindows(ffmpegState))
{
// disable initial burst
initialBurst = 0;
}
_audioInputFile.Iter(a => a.AddOption(new ReadrateInputOption(_ffmpegCapabilities, initialBurst, _logger)));
videoInputFile.AddOption(new ReadrateInputOption(_ffmpegCapabilities, initialBurst, _logger));
}

1
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs

@ -44,6 +44,7 @@ public class PipelineBuilderFactory : IPipelineBuilderFactory @@ -44,6 +44,7 @@ public class PipelineBuilderFactory : IPipelineBuilderFactory
return hardwareAccelerationMode switch
{
HardwareAccelerationMode.Nvenc when capabilities is not NoHardwareCapabilities => new NvidiaPipelineBuilder(
_runtimeInfo,
ffmpegCapabilities,
capabilities,
hardwareAccelerationMode,

2
ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs

@ -37,6 +37,8 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase @@ -37,6 +37,8 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase
protected override bool IsIntelVaapiOrQsv(FFmpegState ffmpegState) => false;
protected override bool IsNvidiaOnWindows(FFmpegState ffmpegState) => false;
protected override FFmpegState SetAccelState(
VideoStream videoStream,
FFmpegState ffmpegState,

Loading…
Cancel
Save