Browse Source

vaapi cqp rate control mode (#1319)

pull/1320/head
Jason Dove 2 years ago committed by GitHub
parent
commit
52e1874426
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 204
      ErsatzTV.FFmpeg.Tests/Capabilities/Vaapi/VaapiCapabilityParserTests.cs
  3. 3
      ErsatzTV.FFmpeg/Capabilities/AmfHardwareCapabilities.cs
  4. 3
      ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs
  5. 18
      ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs
  6. 4
      ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs
  7. 3
      ErsatzTV.FFmpeg/Capabilities/NoHardwareCapabilities.cs
  8. 3
      ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs
  9. 68
      ErsatzTV.FFmpeg/Capabilities/Vaapi/VaapiCapabilityParser.cs
  10. 12
      ErsatzTV.FFmpeg/Capabilities/Vaapi/VaapiProfileEntrypoint.cs
  11. 100
      ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs
  12. 21
      ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderH264Vaapi.cs
  13. 21
      ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderHevcVaapi.cs
  14. 21
      ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderMpeg2Vaapi.cs
  15. 10
      ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs
  16. 11
      ErsatzTV.FFmpeg/RateControlMode.cs

1
CHANGELOG.md

@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Skip checking for subtitles to extract when subtitles are not enabled on a channel/schedule item
- Properly scale subtitles when using hardware acceleration
- Fix color normalization of content with missing color metadata when using NVIDIA acceleration
- `VAAPI`: explicitly use `CQP` rate control mode when it's the only compatible mode
### Changed
- `HLS Direct` streaming mode

204
ErsatzTV.FFmpeg.Tests/Capabilities/Vaapi/VaapiCapabilityParserTests.cs

@ -0,0 +1,204 @@ @@ -0,0 +1,204 @@
using System.Collections.Generic;
using System.Linq;
using ErsatzTV.FFmpeg.Capabilities.Vaapi;
using FluentAssertions;
using NUnit.Framework;
namespace ErsatzTV.FFmpeg.Tests.Capabilities.Vaapi;
[TestFixture]
public class VaapiCapabilityParserTests
{
private const string BriefOutput = @"Trying display: wayland
vainfo: VA-API version: 1.18 (libva 2.18.2)
vainfo: Driver version: Mesa Gallium driver 23.1.2 for AMD Radeon RX 6750 XT (navi22, LLVM 15.0.7, DRM 3.52, 6.3.8-arch1-1)
vainfo: Supported profile and entrypoints
VAProfileMPEG2Simple : VAEntrypointVLD
VAProfileMPEG2Main : VAEntrypointVLD
VAProfileVC1Simple : VAEntrypointVLD
VAProfileVC1Main : VAEntrypointVLD
VAProfileVC1Advanced : VAEntrypointVLD
VAProfileH264ConstrainedBaseline: VAEntrypointVLD
VAProfileH264ConstrainedBaseline: VAEntrypointEncSlice
VAProfileH264Main : VAEntrypointVLD
VAProfileH264Main : VAEntrypointEncSlice
VAProfileH264High : VAEntrypointVLD
VAProfileH264High : VAEntrypointEncSlice
VAProfileHEVCMain : VAEntrypointVLD
VAProfileHEVCMain : VAEntrypointEncSlice
VAProfileHEVCMain10 : VAEntrypointVLD
VAProfileHEVCMain10 : VAEntrypointEncSlice
VAProfileJPEGBaseline : VAEntrypointVLD
VAProfileVP9Profile0 : VAEntrypointVLD
VAProfileVP9Profile2 : VAEntrypointVLD
VAProfileAV1Profile0 : VAEntrypointVLD
VAProfileNone : VAEntrypointVideoProc";
private const string FullOutput = @"Trying display: wayland
vainfo: VA-API version: 1.18 (libva 2.18.2)
vainfo: Driver version: Mesa Gallium driver 23.1.2 for AMD Radeon RX 6750 XT (navi22, LLVM 15.0.7, DRM 3.52, 6.3.8-arch1-1)
vainfo: Supported config attributes per profile/entrypoint pair
VAProfileMPEG2Simple/VAEntrypointVLD
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VAProfileMPEG2Main/VAEntrypointVLD
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VAProfileVC1Simple/VAEntrypointVLD
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VAProfileVC1Main/VAEntrypointVLD
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VAProfileVC1Advanced/VAEntrypointVLD
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VAProfileH264ConstrainedBaseline/VAEntrypointVLD
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VAProfileH264ConstrainedBaseline/VAEntrypointEncSlice
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VAConfigAttribRateControl : VA_RC_CBR
VA_RC_VBR
VA_RC_CQP
VAConfigAttribEncPackedHeaders : VA_ENC_PACKED_HEADER_NONE
VAConfigAttribEncMaxRefFrames : l0=1
l1=0
VAConfigAttribEncMaxSlices : 128
VAConfigAttribEncSliceStructure : VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS
VA_ENC_SLICE_STRUCTURE_EQUAL_ROWS
VAConfigAttribEncQualityRange : number of supported quality levels is 32
VAConfigAttribEncRateControlExt : max_num_temporal_layers_minus1=3 temporal_layer_bitrate_control_flag=1
VAConfigAttribMaxFrameSize : max_frame_size=1
multiple_pass=0
VAProfileH264Main/VAEntrypointVLD
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VAProfileH264Main/VAEntrypointEncSlice
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VAConfigAttribRateControl : VA_RC_CBR
VA_RC_VBR
VA_RC_CQP
VAConfigAttribEncPackedHeaders : VA_ENC_PACKED_HEADER_NONE
VAConfigAttribEncMaxRefFrames : l0=1
l1=0
VAConfigAttribEncMaxSlices : 128
VAConfigAttribEncSliceStructure : VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS
VA_ENC_SLICE_STRUCTURE_EQUAL_ROWS
VAConfigAttribEncQualityRange : number of supported quality levels is 32
VAConfigAttribEncRateControlExt : max_num_temporal_layers_minus1=3 temporal_layer_bitrate_control_flag=1
VAConfigAttribMaxFrameSize : max_frame_size=1
multiple_pass=0
VAProfileH264High/VAEntrypointVLD
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VAProfileH264High/VAEntrypointEncSlice
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VA_RT_FORMAT_YUV420_10
VA_RT_FORMAT_YUV420_10BPP
VAConfigAttribRateControl : VA_RC_CBR
VA_RC_VBR
VA_RC_CQP
VAConfigAttribEncPackedHeaders : VA_ENC_PACKED_HEADER_NONE
VAConfigAttribEncMaxRefFrames : l0=1
l1=0
VAConfigAttribEncMaxSlices : 128
VAConfigAttribEncSliceStructure : VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS
VA_ENC_SLICE_STRUCTURE_EQUAL_ROWS
VAConfigAttribEncQualityRange : number of supported quality levels is 32
VAConfigAttribEncRateControlExt : max_num_temporal_layers_minus1=3 temporal_layer_bitrate_control_flag=1
VAConfigAttribMaxFrameSize : max_frame_size=1
multiple_pass=0
VAProfileHEVCMain/VAEntrypointVLD
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VAProfileHEVCMain/VAEntrypointEncSlice
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VAConfigAttribRateControl : VA_RC_CBR
VA_RC_VBR
VA_RC_CQP
VAConfigAttribEncPackedHeaders : VA_ENC_PACKED_HEADER_SEQUENCE
VAConfigAttribEncMaxRefFrames : l0=1
l1=0
VAConfigAttribEncMaxSlices : 128
VAConfigAttribEncSliceStructure : VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS
VA_ENC_SLICE_STRUCTURE_EQUAL_ROWS
VAConfigAttribEncQualityRange : number of supported quality levels is 32
VAConfigAttribMaxFrameSize : max_frame_size=1
multiple_pass=0
VAProfileHEVCMain10/VAEntrypointVLD
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VA_RT_FORMAT_YUV420_10
VA_RT_FORMAT_YUV420_10BPP
VAProfileHEVCMain10/VAEntrypointEncSlice
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VA_RT_FORMAT_YUV420_10
VA_RT_FORMAT_YUV420_10BPP
VAConfigAttribRateControl : VA_RC_CBR
VA_RC_VBR
VA_RC_CQP
VAConfigAttribEncPackedHeaders : VA_ENC_PACKED_HEADER_SEQUENCE
VAConfigAttribEncMaxRefFrames : l0=1
l1=0
VAConfigAttribEncMaxSlices : 128
VAConfigAttribEncSliceStructure : VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS
VA_ENC_SLICE_STRUCTURE_EQUAL_ROWS
VAConfigAttribEncQualityRange : number of supported quality levels is 32
VAConfigAttribMaxFrameSize : max_frame_size=1
multiple_pass=0
VAProfileJPEGBaseline/VAEntrypointVLD
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VA_RT_FORMAT_YUV422
VA_RT_FORMAT_YUV444
VA_RT_FORMAT_YUV400
VAProfileVP9Profile0/VAEntrypointVLD
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VAProfileVP9Profile2/VAEntrypointVLD
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420_10
VA_RT_FORMAT_YUV420_10BPP
VAProfileAV1Profile0/VAEntrypointVLD
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VA_RT_FORMAT_YUV420_10
VA_RT_FORMAT_YUV420_10BPP
VAProfileNone/VAEntrypointVideoProc
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VA_RT_FORMAT_YUV422
VA_RT_FORMAT_YUV444
VA_RT_FORMAT_YUV400
VA_RT_FORMAT_YUV420_10
VA_RT_FORMAT_RGB32
VA_RT_FORMAT_YUV420_10BPP";
[Test]
public void ShouldParseEntrypoints()
{
List<VaapiProfileEntrypoint> brief = VaapiCapabilityParser.Parse(BriefOutput);
List<VaapiProfileEntrypoint> full = VaapiCapabilityParser.ParseFull(FullOutput);
brief.Count.Should().Be(20);
full.Count.Should().Be(20);
}
[Test]
public void Full_ShouldParseRateControlModes()
{
List<VaapiProfileEntrypoint> full = VaapiCapabilityParser.ParseFull(FullOutput);
full.Count.Should().Be(20);
full.Count(e => e.VaapiEntrypoint.StartsWith("VAEntrypointEnc")).Should().BeGreaterThan(0);
foreach (VaapiProfileEntrypoint entrypoint in full.Where(e => e.VaapiEntrypoint.StartsWith("VAEntrypointEnc")))
{
entrypoint.RateControlModes.Count.Should().BeGreaterThan(0);
}
}
}

3
ErsatzTV.FFmpeg/Capabilities/AmfHardwareCapabilities.cs

@ -27,4 +27,7 @@ public class AmfHardwareCapabilities : IHardwareCapabilities @@ -27,4 +27,7 @@ public class AmfHardwareCapabilities : IHardwareCapabilities
_ => FFmpegCapability.Hardware
};
}
public Option<RateControlMode> GetRateControlMode(string videoFormat, Option<IPixelFormat> maybePixelFormat) =>
Option<RateControlMode>.None;
}

3
ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs

@ -35,4 +35,7 @@ public class DefaultHardwareCapabilities : IHardwareCapabilities @@ -35,4 +35,7 @@ public class DefaultHardwareCapabilities : IHardwareCapabilities
_ => FFmpegCapability.Hardware
};
}
public Option<RateControlMode> GetRateControlMode(string videoFormat, Option<IPixelFormat> maybePixelFormat) =>
Option<RateControlMode>.None;
}

18
ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs

@ -90,7 +90,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory @@ -90,7 +90,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
}
BufferedCommandResult result = await Cli.Wrap("vainfo")
.WithArguments($"--display drm --device {vaapiDevice}")
.WithArguments($"--display drm --device {vaapiDevice} -a")
.WithEnvironmentVariables(envVars)
.WithValidation(CommandResultValidation.None)
.ExecuteBufferedAsync(Encoding.UTF8);
@ -164,22 +164,12 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory @@ -164,22 +164,12 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
return new DefaultHardwareCapabilities();
}
profileEntrypoints = new List<VaapiProfileEntrypoint>();
foreach (string line in string.Join("", output).Split("\n"))
foreach (string o in output)
{
const string PROFILE_ENTRYPOINT_PATTERN = @"(VAProfile\w*).*(VAEntrypoint\w*)";
Match match = Regex.Match(line, PROFILE_ENTRYPOINT_PATTERN);
if (match.Success)
{
profileEntrypoints.Add(
new VaapiProfileEntrypoint(
match.Groups[1].Value.Trim(),
match.Groups[2].Value.Trim()));
}
profileEntrypoints = VaapiCapabilityParser.ParseFull(o);
}
if (profileEntrypoints.Any())
if (profileEntrypoints?.Any() ?? false)
{
_logger.LogInformation(
"Detected {Count} VAAPI profile entrypoints for using {Driver} {Device}",

4
ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs

@ -13,4 +13,8 @@ public interface IHardwareCapabilities @@ -13,4 +13,8 @@ public interface IHardwareCapabilities
string videoFormat,
Option<string> videoProfile,
Option<IPixelFormat> maybePixelFormat);
public Option<RateControlMode> GetRateControlMode(
string videoFormat,
Option<IPixelFormat> maybePixelFormat);
}

3
ErsatzTV.FFmpeg/Capabilities/NoHardwareCapabilities.cs

@ -15,4 +15,7 @@ public class NoHardwareCapabilities : IHardwareCapabilities @@ -15,4 +15,7 @@ public class NoHardwareCapabilities : IHardwareCapabilities
Option<string> videoProfile,
Option<IPixelFormat> maybePixelFormat) =>
FFmpegCapability.Software;
public Option<RateControlMode> GetRateControlMode(string videoFormat, Option<IPixelFormat> maybePixelFormat) =>
Option<RateControlMode>.None;
}

3
ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs

@ -115,4 +115,7 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities @@ -115,4 +115,7 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities
_logger.LogWarning("FFmpeg does not contain codec {Codec}; will fall back to software codec", codec);
return FFmpegCapability.Software;
}
public Option<RateControlMode> GetRateControlMode(string videoFormat, Option<IPixelFormat> maybePixelFormat) =>
Option<RateControlMode>.None;
}

68
ErsatzTV.FFmpeg/Capabilities/Vaapi/VaapiCapabilityParser.cs

@ -0,0 +1,68 @@ @@ -0,0 +1,68 @@
using System.Text.RegularExpressions;
namespace ErsatzTV.FFmpeg.Capabilities.Vaapi;
public static class VaapiCapabilityParser
{
public static List<VaapiProfileEntrypoint> Parse(string output)
{
var profileEntrypoints = new List<VaapiProfileEntrypoint>();
foreach (string line in string.Join("", output).Split("\n"))
{
const string PROFILE_ENTRYPOINT_PATTERN = @"(VAProfile\w*).*(VAEntrypoint\w*)";
Match match = Regex.Match(line, PROFILE_ENTRYPOINT_PATTERN);
if (match.Success)
{
profileEntrypoints.Add(
new VaapiProfileEntrypoint(
match.Groups[1].Value.Trim(),
match.Groups[2].Value.Trim()));
}
}
return profileEntrypoints;
}
public static List<VaapiProfileEntrypoint> ParseFull(string output)
{
var profileEntrypoints = new List<VaapiProfileEntrypoint>();
var profile = new VaapiProfileEntrypoint(string.Empty, string.Empty);
string[] allLines = string.Join("", output).Split("\n");
for (var i = 0; i < allLines.Length; i++)
{
string line = allLines[i];
const string PROFILE_ENTRYPOINT_PATTERN = @"(VAProfile\w*).*(VAEntrypoint\w*)";
const string PROFILE_RATE_CONTROL_PATTERN = @".*VA_RC_(\w*).*";
Match match = Regex.Match(line, PROFILE_ENTRYPOINT_PATTERN);
if (match.Success)
{
profile = new VaapiProfileEntrypoint(match.Groups[1].Value.Trim(), match.Groups[2].Value.Trim());
profileEntrypoints.Add(profile);
}
else
{
// check for rate control
match = Regex.Match(line, PROFILE_RATE_CONTROL_PATTERN);
if (match.Success)
{
switch (match.Groups[1].Value.Trim().ToLowerInvariant())
{
case "cqp":
profile.AddRateControlMode(RateControlMode.CQP);
break;
case "vbr":
profile.AddRateControlMode(RateControlMode.VBR);
break;
case "cbr":
profile.AddRateControlMode(RateControlMode.CBR);
break;
}
}
}
}
return profileEntrypoints;
}
}

12
ErsatzTV.FFmpeg/Capabilities/Vaapi/VaapiProfileEntrypoint.cs

@ -1,3 +1,13 @@ @@ -1,3 +1,13 @@
namespace ErsatzTV.FFmpeg.Capabilities.Vaapi;
public record VaapiProfileEntrypoint(string VaapiProfile, string VaapiEntrypoint);
public record VaapiProfileEntrypoint(string VaapiProfile, string VaapiEntrypoint)
{
private readonly System.Collections.Generic.HashSet<RateControlMode> _rateControlModes = new();
public IReadOnlyCollection<RateControlMode> RateControlModes => _rateControlModes;
public bool AddRateControlMode(RateControlMode mode)
{
return _rateControlModes.Add(mode);
}
}

100
ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs

@ -122,28 +122,32 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities @@ -122,28 +122,32 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
VideoFormat.H264 when bitDepth == 10 => false,
VideoFormat.H264 =>
_profileEntrypoints.Contains(
new VaapiProfileEntrypoint(VaapiProfile.H264Main, VaapiEntrypoint.Encode)) ||
_profileEntrypoints.Contains(
new VaapiProfileEntrypoint(VaapiProfile.H264Main, VaapiEntrypoint.EncodeLowPower)),
_profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.H264Main,
VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower
}),
VideoFormat.Hevc when bitDepth == 10 =>
_profileEntrypoints.Contains(
new VaapiProfileEntrypoint(VaapiProfile.HevcMain10, VaapiEntrypoint.Encode)) ||
_profileEntrypoints.Contains(
new VaapiProfileEntrypoint(VaapiProfile.HevcMain10, VaapiEntrypoint.EncodeLowPower)),
_profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.HevcMain10,
VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower
}),
VideoFormat.Hevc =>
_profileEntrypoints.Contains(
new VaapiProfileEntrypoint(VaapiProfile.HevcMain, VaapiEntrypoint.Encode)) ||
_profileEntrypoints.Contains(
new VaapiProfileEntrypoint(VaapiProfile.HevcMain, VaapiEntrypoint.EncodeLowPower)),
_profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.HevcMain,
VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower
}),
VideoFormat.Mpeg2Video =>
_profileEntrypoints.Contains(
new VaapiProfileEntrypoint(VaapiProfile.Mpeg2Main, VaapiEntrypoint.Encode)) ||
_profileEntrypoints.Contains(
new VaapiProfileEntrypoint(VaapiProfile.Mpeg2Main, VaapiEntrypoint.EncodeLowPower)),
_profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.Mpeg2Main,
VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower
}),
_ => false
};
@ -158,4 +162,68 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities @@ -158,4 +162,68 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
return isHardware ? FFmpegCapability.Hardware : FFmpegCapability.Software;
}
public Option<RateControlMode> GetRateControlMode(string videoFormat, Option<IPixelFormat> maybePixelFormat)
{
int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8);
Option<VaapiProfileEntrypoint> maybeEntrypoint = videoFormat switch
{
// vaapi cannot encode 10-bit h264
VideoFormat.H264 when bitDepth == 10 => None,
VideoFormat.H264 =>
_profileEntrypoints.Where(
e => e is
{
VaapiProfile: VaapiProfile.H264Main,
VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower
})
.HeadOrNone(),
VideoFormat.Hevc when bitDepth == 10 =>
_profileEntrypoints.Where(
e => e is
{
VaapiProfile: VaapiProfile.HevcMain10,
VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower
})
.HeadOrNone(),
VideoFormat.Hevc =>
_profileEntrypoints.Where(
e => e is
{
VaapiProfile: VaapiProfile.HevcMain,
VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower
})
.HeadOrNone(),
VideoFormat.Mpeg2Video =>
_profileEntrypoints.Where(
e => e is
{
VaapiProfile: VaapiProfile.Mpeg2Main,
VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower
})
.HeadOrNone(),
_ => None
};
foreach (VaapiProfileEntrypoint entrypoint in maybeEntrypoint)
{
if (entrypoint.RateControlModes.Contains(RateControlMode.VBR) ||
entrypoint.RateControlModes.Contains(RateControlMode.CBR))
{
return Option<RateControlMode>.None;
}
if (entrypoint.RateControlModes.Contains(RateControlMode.CQP))
{
return RateControlMode.CQP;
}
}
return Option<RateControlMode>.None;
}
}

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

@ -4,8 +4,29 @@ namespace ErsatzTV.FFmpeg.Encoder.Vaapi; @@ -4,8 +4,29 @@ namespace ErsatzTV.FFmpeg.Encoder.Vaapi;
public class EncoderH264Vaapi : EncoderBase
{
private readonly RateControlMode _rateControlMode;
public EncoderH264Vaapi(RateControlMode rateControlMode) => _rateControlMode = rateControlMode;
public override string Name => "h264_vaapi";
public override StreamKind Kind => StreamKind.Video;
public override IList<string> OutputOptions
{
get
{
IList<string> result = base.OutputOptions;
if (_rateControlMode == RateControlMode.CQP)
{
result.Add("-rc_mode");
result.Add("1");
}
return result;
}
}
public override FrameState NextState(FrameState currentState) => currentState with
{

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

@ -4,9 +4,30 @@ namespace ErsatzTV.FFmpeg.Encoder.Vaapi; @@ -4,9 +4,30 @@ namespace ErsatzTV.FFmpeg.Encoder.Vaapi;
public class EncoderHevcVaapi : EncoderBase
{
private readonly RateControlMode _rateControlMode;
public EncoderHevcVaapi(RateControlMode rateControlMode) => _rateControlMode = rateControlMode;
public override string Name => "hevc_vaapi";
public override StreamKind Kind => StreamKind.Video;
public override IList<string> OutputOptions
{
get
{
IList<string> result = base.OutputOptions;
if (_rateControlMode == RateControlMode.CQP)
{
result.Add("-rc_mode");
result.Add("1");
}
return result;
}
}
public override FrameState NextState(FrameState currentState) => currentState with
{
VideoFormat = VideoFormat.Hevc

21
ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderMpeg2Vaapi.cs

@ -4,8 +4,29 @@ namespace ErsatzTV.FFmpeg.Encoder.Vaapi; @@ -4,8 +4,29 @@ namespace ErsatzTV.FFmpeg.Encoder.Vaapi;
public class EncoderMpeg2Vaapi : EncoderBase
{
private readonly RateControlMode _rateControlMode;
public EncoderMpeg2Vaapi(RateControlMode rateControlMode) => _rateControlMode = rateControlMode;
public override string Name => "mpeg2_vaapi";
public override StreamKind Kind => StreamKind.Video;
public override IList<string> OutputOptions
{
get
{
IList<string> result = base.OutputOptions;
if (_rateControlMode == RateControlMode.CQP)
{
result.Add("-rc_mode");
result.Add("1");
}
return result;
}
}
public override FrameState NextState(FrameState currentState) => currentState with
{

10
ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs

@ -197,12 +197,16 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder @@ -197,12 +197,16 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder
// after everything else is done, apply the encoder
if (pipelineSteps.OfType<IEncoder>().All(e => e.Kind != StreamKind.Video))
{
RateControlMode rateControlMode =
_hardwareCapabilities.GetRateControlMode(videoStream.Codec, videoStream.PixelFormat)
.IfNone(RateControlMode.VBR);
Option<IEncoder> maybeEncoder =
(ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch
{
(HardwareAccelerationMode.Vaapi, VideoFormat.Hevc) => new EncoderHevcVaapi(),
(HardwareAccelerationMode.Vaapi, VideoFormat.H264) => new EncoderH264Vaapi(),
(HardwareAccelerationMode.Vaapi, VideoFormat.Mpeg2Video) => new EncoderMpeg2Vaapi(),
(HardwareAccelerationMode.Vaapi, VideoFormat.Hevc) => new EncoderHevcVaapi(rateControlMode),
(HardwareAccelerationMode.Vaapi, VideoFormat.H264) => new EncoderH264Vaapi(rateControlMode),
(HardwareAccelerationMode.Vaapi, VideoFormat.Mpeg2Video) => new EncoderMpeg2Vaapi(rateControlMode),
(_, _) => GetSoftwareEncoder(currentState, desiredState)
};

11
ErsatzTV.FFmpeg/RateControlMode.cs

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
using System.Diagnostics.CodeAnalysis;
namespace ErsatzTV.FFmpeg;
[SuppressMessage("ReSharper", "InconsistentNaming")]
public enum RateControlMode
{
CBR,
CQP,
VBR
}
Loading…
Cancel
Save