< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.MediaEncoding.EncodingHelper
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
Line coverage
3%
Covered lines: 149
Uncovered lines: 3631
Coverable lines: 3780
Total lines: 7942
Line coverage: 3.9%
Branch coverage
2%
Covered branches: 84
Total branches: 3767
Branch coverage: 2.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 2/13/2026 - 12:11:21 AM Line coverage: 0.6% (26/3734) Branch coverage: 0% (0/3729) Total lines: 78183/14/2026 - 12:13:58 AM Line coverage: 0.6% (26/3746) Branch coverage: 0% (0/3735) Total lines: 78513/24/2026 - 12:13:40 AM Line coverage: 0.7% (27/3752) Branch coverage: 0% (0/3739) Total lines: 78623/25/2026 - 12:13:35 AM Line coverage: 0.7% (27/3755) Branch coverage: 0% (0/3739) Total lines: 78693/30/2026 - 12:14:34 AM Line coverage: 0.7% (27/3755) Branch coverage: 0% (0/3739) Total lines: 78773/31/2026 - 12:14:24 AM Line coverage: 0.7% (27/3755) Branch coverage: 0% (0/3743) Total lines: 78754/7/2026 - 12:14:03 AM Line coverage: 0.7% (27/3754) Branch coverage: 0% (0/3743) Total lines: 78894/30/2026 - 12:14:58 AM Line coverage: 0.7% (27/3758) Branch coverage: 0% (0/3751) Total lines: 78935/4/2026 - 12:15:16 AM Line coverage: 0.7% (27/3765) Branch coverage: 0% (0/3757) Total lines: 79055/11/2026 - 12:15:59 AM Line coverage: 0.7% (27/3771) Branch coverage: 0% (0/3765) Total lines: 79165/13/2026 - 12:15:27 AM Line coverage: 0.7% (27/3772) Branch coverage: 0% (0/3765) Total lines: 79175/20/2026 - 12:15:44 AM Line coverage: 3.9% (149/3780) Branch coverage: 2.2% (84/3767) Total lines: 79385/22/2026 - 12:15:17 AM Line coverage: 3.9% (149/3780) Branch coverage: 2.2% (84/3767) Total lines: 7942 2/13/2026 - 12:11:21 AM Line coverage: 0.6% (26/3734) Branch coverage: 0% (0/3729) Total lines: 78183/14/2026 - 12:13:58 AM Line coverage: 0.6% (26/3746) Branch coverage: 0% (0/3735) Total lines: 78513/24/2026 - 12:13:40 AM Line coverage: 0.7% (27/3752) Branch coverage: 0% (0/3739) Total lines: 78623/25/2026 - 12:13:35 AM Line coverage: 0.7% (27/3755) Branch coverage: 0% (0/3739) Total lines: 78693/30/2026 - 12:14:34 AM Line coverage: 0.7% (27/3755) Branch coverage: 0% (0/3739) Total lines: 78773/31/2026 - 12:14:24 AM Line coverage: 0.7% (27/3755) Branch coverage: 0% (0/3743) Total lines: 78754/7/2026 - 12:14:03 AM Line coverage: 0.7% (27/3754) Branch coverage: 0% (0/3743) Total lines: 78894/30/2026 - 12:14:58 AM Line coverage: 0.7% (27/3758) Branch coverage: 0% (0/3751) Total lines: 78935/4/2026 - 12:15:16 AM Line coverage: 0.7% (27/3765) Branch coverage: 0% (0/3757) Total lines: 79055/11/2026 - 12:15:59 AM Line coverage: 0.7% (27/3771) Branch coverage: 0% (0/3765) Total lines: 79165/13/2026 - 12:15:27 AM Line coverage: 0.7% (27/3772) Branch coverage: 0% (0/3765) Total lines: 79175/20/2026 - 12:15:44 AM Line coverage: 3.9% (149/3780) Branch coverage: 2.2% (84/3767) Total lines: 79385/22/2026 - 12:15:17 AM Line coverage: 3.9% (149/3780) Branch coverage: 2.2% (84/3767) Total lines: 7942

Coverage delta

Coverage delta 4 -4

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.cctor()100%210%
GetH264Encoder(...)100%210%
GetH265Encoder(...)100%210%
GetAv1Encoder(...)100%210%
GetH26xOrAv1Encoder(...)0%110100%
GetMjpegEncoder(...)0%210140%
IsVaapiSupported(...)0%2040%
IsVaapiFullSupported()0%272160%
IsRkmppFullSupported()0%4260%
IsOpenclFullSupported()0%4260%
IsCudaFullSupported()0%110100%
IsVulkanFullSupported()0%110100%
IsVideoToolboxFullSupported()0%7280%
IsSwTonemapAvailable(...)0%4260%
IsHwTonemapAvailable(...)0%420200%
IsVulkanHwTonemapAvailable(...)0%4260%
IsIntelVppTonemapAvailable(...)0%342180%
IsVideoToolboxTonemapAvailable(...)0%210140%
IsVideoStreamHevcRext(...)0%342180%
GetVideoEncoder(...)14.28%1091421.42%
GetUserAgentParam(...)0%620%
GetRefererParam(...)0%620%
GetInputFormat(...)0%1482380%
GetDecoderFromCodec(...)0%7280%
InferAudioCodec(...)0%3422580%
InferVideoCodec(...)0%156120%
GetVideoProfileScore(...)0%4260%
GetAudioEncoder(...)0%420200%
GetRkmppDeviceArgs(...)0%620%
GetVideoToolboxDeviceArgs(...)0%620%
GetCudaDeviceArgs(...)0%2040%
GetVulkanDeviceArgs(...)0%7280%
GetOpenclDeviceArgs(...)0%7280%
GetD3d11vaDeviceArgs(...)0%4260%
GetVaapiDeviceArgs(...)0%272160%
GetDrmDeviceArgs(...)0%2040%
GetQsvDeviceArgs(...)0%7280%
GetFilterHwDeviceArgs(...)0%620%
GetGraphicalSubCanvasSize(...)70%322068.75%
GetInputVideoHwaccelArgs(...)2.5%130971203.41%
GetInputArgument(...)52.77%1063662.22%
IsH264(...)0%2040%
IsH265(...)0%2040%
IsAv1(...)0%620%
IsAAC(...)0%620%
IsDoviWithHdr10Bl(...)0%7280%
IsDovi(...)0%110100%
IsHdr10Plus(...)0%7280%
ShouldRemoveDynamicHdrMetadata(...)0%600240%
CanEncoderRemoveDynamicHdrMetadata(...)0%342180%
IsDoviRemoved(...)0%4260%
IsHdr10PlusRemoved(...)0%4260%
GetBitStreamArgs(...)0%702260%
GetAudioBitStreamArguments(...)0%110100%
GetSegmentFileExtension(...)0%620%
GetVideoBitrateParam(...)0%1806420%
GetEncoderParam(...)0%8010890%
NormalizeTranscodingLevel(...)0%506220%
GetTextSubtitlesFilter(...)0%272160%
GetFramerateParam(...)0%156120%
GetHlsVideoKeyFrameArguments(...)0%1806420%
GetVideoQualityParam(...)0%325801800%
CanStreamCopyVideo(...)0%197401400%
CanStreamCopyAudio(...)0%1190340%
GetVideoBitrateParamValue(...)0%506220%
GetMinBitrate(...)0%2040%
GetVideoBitrateScaleFactor(...)0%7280%
ScaleBitrate(...)0%110100%
GetAudioBitrateParam(...)100%210%
GetAudioBitrateParam(...)0%2162460%
GetAudioVbrModeParam(...)0%1190340%
GetAudioFilterParam(...)0%702260%
GetNumAudioChannelsParam(...)0%930300%
EnforceResolutionLimit(...)0%2040%
GetFastSeekCommandLineParameter(...)4.54%1792231.25%
GetMapArgs(...)50%843666.66%
GetNegativeMapArgsByFilters(...)0%2040%
GetMediaStream(...)0%156120%
GetFixedOutputSize(...)0%272160%
IsScaleRatioSupported(...)0%506220%
GetHwScaleFilter(...)0%600240%
GetGraphicalSubPreProcessFilters(...)0%342180%
GetAlphaSrcFilter(...)0%4260%
GetSwScaleFilter(...)0%702260%
GetFixedSwScaleFilter(...)0%132110%
GetSwDeinterlaceFilter(...)0%4260%
GetHwDeinterlaceFilter(...)0%1332360%
GetHwTonemapFilter(...)0%1332360%
GetLibplaceboFilter(...)0%506220%
GetVideoTransposeDirection(...)0%156120%
GetSwVidFilterChain(...)0%2550500%
GetNvidiaVidFilterChain(...)0%110100%
GetNvidiaVidFiltersPrefered(...)0%7832880%
GetAmdVidFilterChain(...)0%210140%
GetAmdDx11VidFiltersPrefered(...)0%7832880%
GetIntelVidFilterChain(...)0%506220%
GetIntelQsvDx11VidFiltersPrefered(...)0%208801440%
GetIntelQsvVaapiVidFiltersPrefered(...)0%175561320%
GetVaapiVidFilterChain(...)0%1190340%
GetIntelVaapiFullVidFiltersPrefered(...)0%126561120%
GetAmdVaapiFullVidFiltersPrefered(...)0%101001000%
GetVaapiLimitedVidFiltersPrefered(...)0%8190900%
GetAppleVidFilterChain(...)0%156120%
GetAppleVidFiltersPreferred(...)0%5550740%
GetRkmppVidFilterChain(...)0%272160%
GetRkmppVidFiltersPrefered(...)0%160021260%
GetVideoProcessingFilterParam(...)0%2756520%
GetOverwriteColorPropertiesParam(...)0%2040%
GetInputHdrParam(...)0%620%
GetOutputSdrParam(...)0%2040%
GetVideoColorBitDepth(...)0%600240%
GetHardwareVideoDecoder(...)17.74%16846225%
GetHwDecoderName(...)0%1190340%
GetHwaccelType(...)0%150061220%
GetQsvHwVidDecoder(...)0%3192560%
GetNvdecVidDecoder(...)0%2162460%
GetAmfVidDecoder(...)0%1056320%
GetVaapiVidDecoder(...)0%2756520%
GetVideotoolboxVidDecoder(...)0%2352480%
GetRkmppVidDecoder(...)0%2756520%
GetNumberOfThreads(...)0%4260%
TryStreamCopy(...)0%600240%
GetFfmpegAnalyzeDurationArg(...)50%7671.42%
GetFfmpegProbesizeArg()50%2275%
GetInputModifier(...)0%3660600%
AttachMediaSourceInfo(...)0%1806420%
ShiftAudioCodecsIfNeeded(...)0%110100%
ShiftVideoCodecsIfNeeded(...)0%110100%
NormalizeSubtitleEmbed(...)0%4260%
GetSubtitleEmbedArguments(...)0%7280%
GetProgressiveVideoFullCommandLine(...)0%2040%
GetOutputFFlags(...)0%2040%
GetProgressiveVideoArguments(...)0%1482380%
GetProgressiveVideoAudioArguments(...)0%600240%
GetProgressiveAudioFullCommandLine(...)0%930300%
FindIndex(...)66.66%6688.88%
IsCopyCodec(...)100%11100%
ShouldEncodeSubtitle(...)75%44100%
NeedsExternalSubtitleMuxing(...)62.5%88100%
GetVideoSyncOption(...)0%132110%

File(s)

/srv/git/jellyfin/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CS1591
 4// We need lowercase normalized string for ffmpeg
 5#pragma warning disable CA1308
 6
 7using System;
 8using System.Collections.Generic;
 9using System.Globalization;
 10using System.IO;
 11using System.Linq;
 12using System.Runtime.InteropServices;
 13using System.Text;
 14using System.Text.RegularExpressions;
 15using System.Threading;
 16using Jellyfin.Data;
 17using Jellyfin.Data.Enums;
 18using Jellyfin.Database.Implementations.Enums;
 19using Jellyfin.Extensions;
 20using MediaBrowser.Common.Configuration;
 21using MediaBrowser.Controller.Extensions;
 22using MediaBrowser.Controller.IO;
 23using MediaBrowser.Model.Configuration;
 24using MediaBrowser.Model.Dlna;
 25using MediaBrowser.Model.Dto;
 26using MediaBrowser.Model.Entities;
 27using MediaBrowser.Model.MediaInfo;
 28using Microsoft.Extensions.Configuration;
 29using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
 30
 31namespace MediaBrowser.Controller.MediaEncoding
 32{
 33    public partial class EncodingHelper
 34    {
 35        /// <summary>
 36        /// The codec validation regex string.
 37        /// This regular expression matches strings that consist of alphanumeric characters, hyphens,
 38        /// periods, underscores, commas, and vertical bars, with a length between 0 and 40 characters.
 39        /// This should matches all common valid codecs.
 40        /// </summary>
 41        public const string ContainerValidationRegexStr = @"^[a-zA-Z0-9\-\._,|]{0,40}$";
 42
 43        /// <summary>
 44        /// The level validation regex string.
 45        /// This regular expression matches strings representing a double.
 46        /// </summary>
 47        public const string LevelValidationRegexStr = @"-?[0-9]+(?:\.[0-9]+)?";
 48
 49        private const string _defaultMjpegEncoder = "mjpeg";
 50
 51        private const string QsvAlias = "qs";
 52        private const string VaapiAlias = "va";
 53        private const string D3d11vaAlias = "dx11";
 54        private const string VideotoolboxAlias = "vt";
 55        private const string RkmppAlias = "rk";
 56        private const string OpenclAlias = "ocl";
 57        private const string CudaAlias = "cu";
 58        private const string DrmAlias = "dr";
 59        private const string VulkanAlias = "vk";
 60        private readonly IApplicationPaths _appPaths;
 61        private readonly IMediaEncoder _mediaEncoder;
 62        private readonly ISubtitleEncoder _subtitleEncoder;
 63        private readonly IConfiguration _config;
 64        private readonly IConfigurationManager _configurationManager;
 65        private readonly IPathManager _pathManager;
 66
 67        // i915 hang was fixed by linux 6.2 (3f882f2)
 3268        private readonly Version _minKerneli915Hang = new Version(5, 18);
 3269        private readonly Version _maxKerneli915Hang = new Version(6, 1, 3);
 3270        private readonly Version _minFixedKernel60i915Hang = new Version(6, 0, 18);
 3271        private readonly Version _minKernelVersionAmdVkFmtModifier = new Version(5, 15);
 72
 3273        private readonly Version _minFFmpegImplicitHwaccel = new Version(6, 0);
 3274        private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0);
 3275        private readonly Version _minFFmpegOclCuTonemapMode = new Version(5, 1, 3);
 3276        private readonly Version _minFFmpegSvtAv1Params = new Version(5, 1);
 3277        private readonly Version _minFFmpegVaapiH26xEncA53CcSei = new Version(6, 0);
 3278        private readonly Version _minFFmpegReadrateOption = new Version(5, 0);
 3279        private readonly Version _minFFmpegWorkingVtHwSurface = new Version(7, 0, 1);
 3280        private readonly Version _minFFmpegDisplayRotationOption = new Version(6, 0);
 3281        private readonly Version _minFFmpegAdvancedTonemapMode = new Version(7, 0, 1);
 3282        private readonly Version _minFFmpegAlteredVaVkInterop = new Version(7, 0, 1);
 3283        private readonly Version _minFFmpegQsvVppTonemapOption = new Version(7, 0, 1);
 3284        private readonly Version _minFFmpegQsvVppOutRangeOption = new Version(7, 0, 1);
 3285        private readonly Version _minFFmpegVaapiDeviceVendorId = new Version(7, 0, 1);
 3286        private readonly Version _minFFmpegQsvVppScaleModeOption = new Version(6, 0);
 3287        private readonly Version _minFFmpegRkmppHevcDecDoviRpu = new Version(7, 1, 1);
 3288        private readonly Version _minFFmpegReadrateCatchupOption = new Version(8, 0);
 89
 090        private static readonly string[] _videoProfilesH264 =
 091        [
 092            "ConstrainedBaseline",
 093            "Baseline",
 094            "Extended",
 095            "Main",
 096            "High",
 097            "ProgressiveHigh",
 098            "ConstrainedHigh",
 099            "High10"
 0100        ];
 101
 0102        private static readonly string[] _videoProfilesH265 =
 0103        [
 0104            "Main",
 0105            "Main10"
 0106        ];
 107
 0108        private static readonly string[] _videoProfilesAv1 =
 0109        [
 0110            "Main",
 0111            "High",
 0112            "Professional",
 0113        ];
 114
 0115        private static readonly HashSet<string> _mp4ContainerNames = new(StringComparer.OrdinalIgnoreCase)
 0116        {
 0117            "mp4",
 0118            "m4a",
 0119            "m4p",
 0120            "m4b",
 0121            "m4r",
 0122            "m4v",
 0123        };
 124
 0125        private static readonly TonemappingMode[] _legacyTonemapModes = [TonemappingMode.max, TonemappingMode.rgb];
 0126        private static readonly TonemappingMode[] _advancedTonemapModes = [TonemappingMode.lum, TonemappingMode.itp];
 127
 128        // Set max transcoding channels for encoders that can't handle more than a set amount of channels
 129        // AAC, FLAC, ALAC, libopus, libvorbis encoders all support at least 8 channels
 0130        private static readonly Dictionary<string, int> _audioTranscodeChannelLookup = new(StringComparer.OrdinalIgnoreC
 0131        {
 0132            { "libmp3lame", 2 },
 0133            { "libfdk_aac", 6 },
 0134            { "ac3", 6 },
 0135            { "eac3", 6 },
 0136            { "dca", 6 },
 0137            { "mlp", 6 },
 0138            { "truehd", 6 },
 0139        };
 140
 0141        private static readonly Dictionary<HardwareAccelerationType, string> _mjpegCodecMap = new()
 0142        {
 0143            { HardwareAccelerationType.vaapi, _defaultMjpegEncoder + "_vaapi" },
 0144            { HardwareAccelerationType.qsv, _defaultMjpegEncoder + "_qsv" },
 0145            { HardwareAccelerationType.videotoolbox, _defaultMjpegEncoder + "_videotoolbox" },
 0146            { HardwareAccelerationType.rkmpp, _defaultMjpegEncoder + "_rkmpp" }
 0147        };
 148
 0149        public static readonly string[] LosslessAudioCodecs =
 0150        [
 0151            "alac",
 0152            "ape",
 0153            "flac",
 0154            "mlp",
 0155            "truehd",
 0156            "wavpack"
 0157        ];
 158
 159        public EncodingHelper(
 160            IApplicationPaths appPaths,
 161            IMediaEncoder mediaEncoder,
 162            ISubtitleEncoder subtitleEncoder,
 163            IConfiguration config,
 164            IConfigurationManager configurationManager,
 165            IPathManager pathManager)
 166        {
 32167            _appPaths = appPaths;
 32168            _mediaEncoder = mediaEncoder;
 32169            _subtitleEncoder = subtitleEncoder;
 32170            _config = config;
 32171            _configurationManager = configurationManager;
 32172            _pathManager = pathManager;
 32173        }
 174
 175        private enum DynamicHdrMetadataRemovalPlan
 176        {
 177            None,
 178            RemoveDovi,
 179            RemoveHdr10Plus,
 180        }
 181
 182        /// <summary>
 183        /// The codec validation regex.
 184        /// This regular expression matches strings that consist of alphanumeric characters, hyphens,
 185        /// periods, underscores, commas, and vertical bars, with a length between 0 and 40 characters.
 186        /// This should matches all common valid codecs.
 187        /// </summary>
 188        [GeneratedRegex(ContainerValidationRegexStr)]
 189        public static partial Regex ContainerValidationRegex();
 190
 191        /// <summary>
 192        /// The level validation regex string.
 193        /// This regular expression matches strings representing a double.
 194        /// </summary>
 195        [GeneratedRegex(LevelValidationRegexStr)]
 196        public static partial Regex LevelValidationRegex();
 197
 198        [GeneratedRegex(@"\s+")]
 199        private static partial Regex WhiteSpaceRegex();
 200
 201        public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0202            => GetH26xOrAv1Encoder("libx264", "h264", state, encodingOptions);
 203
 204        public string GetH265Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0205            => GetH26xOrAv1Encoder("libx265", "hevc", state, encodingOptions);
 206
 207        public string GetAv1Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0208            => GetH26xOrAv1Encoder("libsvtav1", "av1", state, encodingOptions);
 209
 210        private string GetH26xOrAv1Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptio
 211        {
 212            // Only use alternative encoders for video files.
 213            // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying
 214            // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such 
 0215            if (state.VideoType == VideoType.VideoFile)
 216            {
 0217                var hwType = encodingOptions.HardwareAccelerationType;
 218
 0219                var codecMap = new Dictionary<HardwareAccelerationType, string>()
 0220                {
 0221                    { HardwareAccelerationType.amf,                  hwEncoder + "_amf" },
 0222                    { HardwareAccelerationType.nvenc,                hwEncoder + "_nvenc" },
 0223                    { HardwareAccelerationType.qsv,                  hwEncoder + "_qsv" },
 0224                    { HardwareAccelerationType.vaapi,                hwEncoder + "_vaapi" },
 0225                    { HardwareAccelerationType.videotoolbox,         hwEncoder + "_videotoolbox" },
 0226                    { HardwareAccelerationType.v4l2m2m,              hwEncoder + "_v4l2m2m" },
 0227                    { HardwareAccelerationType.rkmpp,                hwEncoder + "_rkmpp" },
 0228                };
 229
 0230                if (hwType != HardwareAccelerationType.none
 0231                    && encodingOptions.EnableHardwareEncoding
 0232                    && codecMap.TryGetValue(hwType, out var preferredEncoder)
 0233                    && _mediaEncoder.SupportsEncoder(preferredEncoder))
 234                {
 0235                    return preferredEncoder;
 236                }
 237            }
 238
 0239            return defaultEncoder;
 240        }
 241
 242        private string GetMjpegEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 243        {
 0244            if (state.VideoType == VideoType.VideoFile)
 245            {
 0246                var hwType = encodingOptions.HardwareAccelerationType;
 247
 248                // Only enable VA-API MJPEG encoder on Intel iHD driver.
 249                // Legacy platforms supported ONLY by i965 do not support MJPEG encoder.
 0250                if (hwType == HardwareAccelerationType.vaapi
 0251                    && !_mediaEncoder.IsVaapiDeviceInteliHD)
 252                {
 0253                    return _defaultMjpegEncoder;
 254                }
 255
 0256                if (hwType != HardwareAccelerationType.none
 0257                    && encodingOptions.EnableHardwareEncoding
 0258                    && _mjpegCodecMap.TryGetValue(hwType, out var preferredEncoder)
 0259                    && _mediaEncoder.SupportsEncoder(preferredEncoder))
 260                {
 0261                    return preferredEncoder;
 262                }
 263            }
 264
 0265            return _defaultMjpegEncoder;
 266        }
 267
 268        private bool IsVaapiSupported(EncodingJobInfo state)
 269        {
 270            // vaapi will throw an error with this input
 271            // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99.
 0272            if (string.Equals(state.VideoStream?.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 273            {
 0274                return false;
 275            }
 276
 0277            return _mediaEncoder.SupportsHwaccel("vaapi");
 278        }
 279
 280        private bool IsVaapiFullSupported()
 281        {
 0282            return _mediaEncoder.SupportsHwaccel("drm")
 0283                   && _mediaEncoder.SupportsHwaccel("vaapi")
 0284                   && _mediaEncoder.SupportsFilter("scale_vaapi")
 0285                   && _mediaEncoder.SupportsFilter("deinterlace_vaapi")
 0286                   && _mediaEncoder.SupportsFilter("tonemap_vaapi")
 0287                   && _mediaEncoder.SupportsFilter("procamp_vaapi")
 0288                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVaapiFrameSync)
 0289                   && _mediaEncoder.SupportsFilter("transpose_vaapi")
 0290                   && _mediaEncoder.SupportsFilter("hwupload_vaapi");
 291        }
 292
 293        private bool IsRkmppFullSupported()
 294        {
 0295            return _mediaEncoder.SupportsHwaccel("rkmpp")
 0296                   && _mediaEncoder.SupportsFilter("scale_rkrga")
 0297                   && _mediaEncoder.SupportsFilter("vpp_rkrga")
 0298                   && _mediaEncoder.SupportsFilter("overlay_rkrga");
 299        }
 300
 301        private bool IsOpenclFullSupported()
 302        {
 0303            return _mediaEncoder.SupportsHwaccel("opencl")
 0304                   && _mediaEncoder.SupportsFilter("scale_opencl")
 0305                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390)
 0306                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclFrameSync);
 307
 308            // Let transpose_opencl optional for the time being.
 309        }
 310
 311        private bool IsCudaFullSupported()
 312        {
 0313            return _mediaEncoder.SupportsHwaccel("cuda")
 0314                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat)
 0315                   && _mediaEncoder.SupportsFilter("yadif_cuda")
 0316                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName)
 0317                   && _mediaEncoder.SupportsFilter("overlay_cuda")
 0318                   && _mediaEncoder.SupportsFilter("hwupload_cuda");
 319
 320            // Let transpose_cuda optional for the time being.
 321        }
 322
 323        private bool IsVulkanFullSupported()
 324        {
 0325            return _mediaEncoder.SupportsHwaccel("vulkan")
 0326                   && _mediaEncoder.SupportsFilter("libplacebo")
 0327                   && _mediaEncoder.SupportsFilter("scale_vulkan")
 0328                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVulkanFrameSync)
 0329                   && _mediaEncoder.SupportsFilter("transpose_vulkan")
 0330                   && _mediaEncoder.SupportsFilter("flip_vulkan");
 331        }
 332
 333        private bool IsVideoToolboxFullSupported()
 334        {
 0335            return _mediaEncoder.SupportsHwaccel("videotoolbox")
 0336                && _mediaEncoder.SupportsFilter("yadif_videotoolbox")
 0337                && _mediaEncoder.SupportsFilter("overlay_videotoolbox")
 0338                && _mediaEncoder.SupportsFilter("tonemap_videotoolbox")
 0339                && _mediaEncoder.SupportsFilter("scale_vt");
 340
 341            // Let transpose_vt optional for the time being.
 342        }
 343
 344        private bool IsSwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 345        {
 0346            if (state.VideoStream is null
 0347                || GetVideoColorBitDepth(state) < 10
 0348                || !_mediaEncoder.SupportsFilter("tonemapx"))
 349            {
 0350                return false;
 351            }
 352
 0353            return state.VideoStream.VideoRange == VideoRange.HDR;
 354        }
 355
 356        private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 357        {
 0358            if (state.VideoStream is null
 0359                || !options.EnableTonemapping
 0360                || GetVideoColorBitDepth(state) < 10)
 361            {
 0362                return false;
 363            }
 364
 0365            if (state.VideoStream.VideoRange == VideoRange.HDR
 0366                && state.VideoStream.VideoRangeType == VideoRangeType.DOVI)
 367            {
 368                // Only native SW decoder, HW accelerator and hevc_rkmpp decoder can parse dovi rpu.
 0369                var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 370
 0371                var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 0372                if (isRkmppDecoder
 0373                    && _mediaEncoder.EncoderVersion >= _minFFmpegRkmppHevcDecDoviRpu
 0374                    && string.Equals(state.VideoStream?.Codec, "hevc", StringComparison.OrdinalIgnoreCase))
 375                {
 0376                    return true;
 377                }
 378
 0379                var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 0380                var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 0381                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 0382                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 0383                var isVideoToolBoxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 0384                return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder || isVideoToolBoxDecoder;
 385            }
 386
 387            // GPU tonemapping supports all HDR RangeTypes
 0388            return state.VideoStream.VideoRange == VideoRange.HDR;
 389        }
 390
 391        private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 392        {
 0393            if (state.VideoStream is null)
 394            {
 0395                return false;
 396            }
 397
 398            // libplacebo has partial Dolby Vision to SDR tonemapping support.
 0399            return options.EnableTonemapping
 0400                   && state.VideoStream.VideoRange == VideoRange.HDR
 0401                   && GetVideoColorBitDepth(state) == 10;
 402        }
 403
 404        private bool IsIntelVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 405        {
 0406            if (state.VideoStream is null
 0407                || !options.EnableVppTonemapping
 0408                || GetVideoColorBitDepth(state) < 10)
 409            {
 0410                return false;
 411            }
 412
 413            // prefer 'tonemap_vaapi' over 'vpp_qsv' on Linux for supporting Gen9/KBLx.
 414            // 'vpp_qsv' requires VPL, which is only supported on Gen12/TGLx and newer.
 0415            if (OperatingSystem.IsWindows()
 0416                && options.HardwareAccelerationType == HardwareAccelerationType.qsv
 0417                && _mediaEncoder.EncoderVersion < _minFFmpegQsvVppTonemapOption)
 418            {
 0419                return false;
 420            }
 421
 0422            return state.VideoStream.VideoRange == VideoRange.HDR
 0423                   && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
 0424                       || IsHdr10Plus(state.VideoStream)
 0425                       || IsDoviWithHdr10Bl(state.VideoStream));
 426        }
 427
 428        private bool IsVideoToolboxTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 429        {
 0430            if (state.VideoStream is null
 0431                || !options.EnableVideoToolboxTonemapping
 0432                || GetVideoColorBitDepth(state) < 10)
 433            {
 0434                return false;
 435            }
 436
 437            // Certain DV profile 5 video works in Safari with direct playing, but the VideoToolBox does not produce cor
 438            // All other HDR formats working.
 0439            return state.VideoStream.VideoRange == VideoRange.HDR
 0440                   && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
 0441                       || IsHdr10Plus(state.VideoStream)
 0442                       || IsDoviWithHdr10Bl(state.VideoStream)
 0443                       || state.VideoStream.VideoRangeType == VideoRangeType.HLG);
 444        }
 445
 446        private bool IsVideoStreamHevcRext(EncodingJobInfo state)
 447        {
 0448            var videoStream = state.VideoStream;
 0449            if (videoStream is null)
 450            {
 0451                return false;
 452            }
 453
 0454            return string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 0455                   && (string.Equals(videoStream.Profile, "Rext", StringComparison.OrdinalIgnoreCase)
 0456                       || string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 0457                       || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 0458                       || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 0459                       || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 0460                       || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase)
 0461                       || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)
 0462                       || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase));
 463        }
 464
 465        /// <summary>
 466        /// Gets the name of the output video codec.
 467        /// </summary>
 468        /// <param name="state">Encoding state.</param>
 469        /// <param name="encodingOptions">Encoding options.</param>
 470        /// <returns>Encoder string.</returns>
 471        public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 472        {
 4473            var codec = state.OutputVideoCodec;
 474
 4475            if (!string.IsNullOrEmpty(codec))
 476            {
 0477                if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 478                {
 0479                    return GetAv1Encoder(state, encodingOptions);
 480                }
 481
 0482                if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 0483                    || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
 484                {
 0485                    return GetH265Encoder(state, encodingOptions);
 486                }
 487
 0488                if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
 489                {
 0490                    return GetH264Encoder(state, encodingOptions);
 491                }
 492
 0493                if (string.Equals(codec, "mjpeg", StringComparison.OrdinalIgnoreCase))
 494                {
 0495                    return GetMjpegEncoder(state, encodingOptions);
 496                }
 497
 0498                if (ContainerValidationRegex().IsMatch(codec))
 499                {
 0500                    return codec.ToLowerInvariant();
 501                }
 502            }
 503
 4504            return "copy";
 505        }
 506
 507        /// <summary>
 508        /// Gets the user agent param.
 509        /// </summary>
 510        /// <param name="state">The state.</param>
 511        /// <returns>System.String.</returns>
 512        public string GetUserAgentParam(EncodingJobInfo state)
 513        {
 0514            if (state.RemoteHttpHeaders.TryGetValue("User-Agent", out string useragent))
 515            {
 0516                return "-user_agent \"" + useragent + "\"";
 517            }
 518
 0519            return string.Empty;
 520        }
 521
 522        /// <summary>
 523        /// Gets the referer param.
 524        /// </summary>
 525        /// <param name="state">The state.</param>
 526        /// <returns>System.String.</returns>
 527        public string GetRefererParam(EncodingJobInfo state)
 528        {
 0529            if (state.RemoteHttpHeaders.TryGetValue("Referer", out string referer))
 530            {
 0531                return "-referer \"" + referer + "\"";
 532            }
 533
 0534            return string.Empty;
 535        }
 536
 537        public static string GetInputFormat(string container)
 538        {
 0539            if (string.IsNullOrEmpty(container) || !ContainerValidationRegex().IsMatch(container))
 540            {
 0541                return null;
 542            }
 543
 0544            container = container.Replace("mkv", "matroska", StringComparison.OrdinalIgnoreCase);
 545
 0546            if (string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
 547            {
 0548                return "mpegts";
 549            }
 550
 551            // For these need to find out the ffmpeg names
 0552            if (string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase))
 553            {
 0554                return null;
 555            }
 556
 0557            if (string.Equals(container, "wmv", StringComparison.OrdinalIgnoreCase))
 558            {
 0559                return null;
 560            }
 561
 0562            if (string.Equals(container, "mts", StringComparison.OrdinalIgnoreCase))
 563            {
 0564                return null;
 565            }
 566
 0567            if (string.Equals(container, "vob", StringComparison.OrdinalIgnoreCase))
 568            {
 0569                return null;
 570            }
 571
 0572            if (string.Equals(container, "mpg", StringComparison.OrdinalIgnoreCase))
 573            {
 0574                return null;
 575            }
 576
 0577            if (string.Equals(container, "mpeg", StringComparison.OrdinalIgnoreCase))
 578            {
 0579                return null;
 580            }
 581
 0582            if (string.Equals(container, "rec", StringComparison.OrdinalIgnoreCase))
 583            {
 0584                return null;
 585            }
 586
 0587            if (string.Equals(container, "dvr-ms", StringComparison.OrdinalIgnoreCase))
 588            {
 0589                return null;
 590            }
 591
 0592            if (string.Equals(container, "ogm", StringComparison.OrdinalIgnoreCase))
 593            {
 0594                return null;
 595            }
 596
 0597            if (string.Equals(container, "divx", StringComparison.OrdinalIgnoreCase))
 598            {
 0599                return null;
 600            }
 601
 0602            if (string.Equals(container, "tp", StringComparison.OrdinalIgnoreCase))
 603            {
 0604                return null;
 605            }
 606
 0607            if (string.Equals(container, "rmvb", StringComparison.OrdinalIgnoreCase))
 608            {
 0609                return null;
 610            }
 611
 0612            if (string.Equals(container, "rtp", StringComparison.OrdinalIgnoreCase))
 613            {
 0614                return null;
 615            }
 616
 617            // Seeing reported failures here, not sure yet if this is related to specifying input format
 0618            if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase))
 619            {
 0620                return null;
 621            }
 622
 623            // obviously don't do this for strm files
 0624            if (string.Equals(container, "strm", StringComparison.OrdinalIgnoreCase))
 625            {
 0626                return null;
 627            }
 628
 629            // ISO files don't have an ffmpeg format
 0630            if (string.Equals(container, "iso", StringComparison.OrdinalIgnoreCase))
 631            {
 0632                return null;
 633            }
 634
 0635            return container;
 636        }
 637
 638        /// <summary>
 639        /// Gets decoder from a codec.
 640        /// </summary>
 641        /// <param name="codec">Codec to use.</param>
 642        /// <returns>Decoder string.</returns>
 643        public string GetDecoderFromCodec(string codec)
 644        {
 645            // For these need to find out the ffmpeg names
 0646            if (string.Equals(codec, "mp2", StringComparison.OrdinalIgnoreCase))
 647            {
 0648                return null;
 649            }
 650
 0651            if (string.Equals(codec, "aac_latm", StringComparison.OrdinalIgnoreCase))
 652            {
 0653                return null;
 654            }
 655
 0656            if (string.Equals(codec, "eac3", StringComparison.OrdinalIgnoreCase))
 657            {
 0658                return null;
 659            }
 660
 0661            if (_mediaEncoder.SupportsDecoder(codec))
 662            {
 0663                return codec;
 664            }
 665
 0666            return null;
 667        }
 668
 669        /// <summary>
 670        /// Infers the audio codec based on the url.
 671        /// </summary>
 672        /// <param name="container">Container to use.</param>
 673        /// <returns>Codec string.</returns>
 674        public string InferAudioCodec(string container)
 675        {
 0676            if (string.IsNullOrWhiteSpace(container))
 677            {
 678                // this may not work, but if the client is that broken we cannot do anything better
 0679                return "aac";
 680            }
 681
 0682            var inferredCodec = container.ToLowerInvariant();
 683
 0684            return inferredCodec switch
 0685            {
 0686                "ogg" or "oga" or "ogv" or "webm" or "webma" => "opus",
 0687                "m4a" or "m4b" or "mp4" or "mov" or "mkv" or "mka" => "aac",
 0688                "ts" or "avi" or "flv" or "f4v" or "swf" => "mp3",
 0689                _ => inferredCodec
 0690            };
 691        }
 692
 693        /// <summary>
 694        /// Infers the video codec.
 695        /// </summary>
 696        /// <param name="url">The URL.</param>
 697        /// <returns>System.Nullable{VideoCodecs}.</returns>
 698        public string InferVideoCodec(string url)
 699        {
 0700            var ext = Path.GetExtension(url.AsSpan());
 701
 0702            if (ext.Equals(".asf", StringComparison.OrdinalIgnoreCase))
 703            {
 0704                return "wmv";
 705            }
 706
 0707            if (ext.Equals(".webm", StringComparison.OrdinalIgnoreCase))
 708            {
 709                // TODO: this may not always mean VP8, as the codec ages
 0710                return "vp8";
 711            }
 712
 0713            if (ext.Equals(".ogg", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ogv", StringComparison.OrdinalIgn
 714            {
 0715                return "theora";
 716            }
 717
 0718            if (ext.Equals(".m3u8", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ts", StringComparison.OrdinalIgn
 719            {
 0720                return "h264";
 721            }
 722
 0723            return "copy";
 724        }
 725
 726        public int GetVideoProfileScore(string videoCodec, string videoProfile)
 727        {
 728            // strip spaces because they may be stripped out on the query string
 0729            string profile = videoProfile.Replace(" ", string.Empty, StringComparison.Ordinal);
 0730            if (string.Equals("h264", videoCodec, StringComparison.OrdinalIgnoreCase))
 731            {
 0732                return Array.FindIndex(_videoProfilesH264, x => string.Equals(x, profile, StringComparison.OrdinalIgnore
 733            }
 734
 0735            if (string.Equals("hevc", videoCodec, StringComparison.OrdinalIgnoreCase))
 736            {
 0737                return Array.FindIndex(_videoProfilesH265, x => string.Equals(x, profile, StringComparison.OrdinalIgnore
 738            }
 739
 0740            if (string.Equals("av1", videoCodec, StringComparison.OrdinalIgnoreCase))
 741            {
 0742                return Array.FindIndex(_videoProfilesAv1, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreC
 743            }
 744
 0745            return -1;
 746        }
 747
 748        /// <summary>
 749        /// Gets the audio encoder.
 750        /// </summary>
 751        /// <param name="state">The state.</param>
 752        /// <returns>System.String.</returns>
 753        public string GetAudioEncoder(EncodingJobInfo state)
 754        {
 0755            var codec = state.OutputAudioCodec;
 756
 0757            if (!ContainerValidationRegex().IsMatch(codec))
 758            {
 0759                codec = "aac";
 760            }
 761
 0762            if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
 763            {
 764                // Use Apple's aac encoder if available as it provides best audio quality
 0765                if (_mediaEncoder.SupportsEncoder("aac_at"))
 766                {
 0767                    return "aac_at";
 768                }
 769
 770                // Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
 0771                if (_mediaEncoder.SupportsEncoder("libfdk_aac"))
 772                {
 0773                    return "libfdk_aac";
 774                }
 775
 0776                return "aac";
 777            }
 778
 0779            if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
 780            {
 0781                return "libmp3lame";
 782            }
 783
 0784            if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase))
 785            {
 0786                return "libvorbis";
 787            }
 788
 0789            if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
 790            {
 0791                return "libopus";
 792            }
 793
 0794            if (string.Equals(codec, "flac", StringComparison.OrdinalIgnoreCase))
 795            {
 0796                return "flac";
 797            }
 798
 0799            if (string.Equals(codec, "dts", StringComparison.OrdinalIgnoreCase))
 800            {
 0801                return "dca";
 802            }
 803
 0804            if (string.Equals(codec, "alac", StringComparison.OrdinalIgnoreCase))
 805            {
 806                // The ffmpeg upstream breaks the AudioToolbox ALAC encoder in version 6.1 but fixes it in version 7.0.
 807                // Since ALAC is lossless in quality and the AudioToolbox encoder is not faster,
 808                // its only benefit is a smaller file size.
 809                // To prevent problems, use the ffmpeg native encoder instead.
 0810                return "alac";
 811            }
 812
 0813            return codec.ToLowerInvariant();
 814        }
 815
 816        private string GetRkmppDeviceArgs(string alias)
 817        {
 0818            alias ??= RkmppAlias;
 819
 820            // device selection in rk is not supported.
 0821            return " -init_hw_device rkmpp=" + alias;
 822        }
 823
 824        private string GetVideoToolboxDeviceArgs(string alias)
 825        {
 0826            alias ??= VideotoolboxAlias;
 827
 828            // device selection in vt is not supported.
 0829            return " -init_hw_device videotoolbox=" + alias;
 830        }
 831
 832        private string GetCudaDeviceArgs(int deviceIndex, string alias)
 833        {
 0834            alias ??= CudaAlias;
 0835            deviceIndex = deviceIndex >= 0
 0836                ? deviceIndex
 0837                : 0;
 838
 0839            return string.Format(
 0840                CultureInfo.InvariantCulture,
 0841                " -init_hw_device cuda={0}:{1}",
 0842                alias,
 0843                deviceIndex);
 844        }
 845
 846        private string GetVulkanDeviceArgs(int deviceIndex, string deviceName, string srcDeviceAlias, string alias)
 847        {
 0848            alias ??= VulkanAlias;
 0849            deviceIndex = deviceIndex >= 0
 0850                ? deviceIndex
 0851                : 0;
 0852            var vendorOpts = string.IsNullOrEmpty(deviceName)
 0853                ? ":" + deviceIndex
 0854                : ":" + "\"" + deviceName + "\"";
 0855            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0856                ? vendorOpts
 0857                : "@" + srcDeviceAlias;
 858
 0859            return string.Format(
 0860                CultureInfo.InvariantCulture,
 0861                " -init_hw_device vulkan={0}{1}",
 0862                alias,
 0863                options);
 864        }
 865
 866        private string GetOpenclDeviceArgs(int deviceIndex, string deviceVendorName, string srcDeviceAlias, string alias
 867        {
 0868            alias ??= OpenclAlias;
 0869            deviceIndex = deviceIndex >= 0
 0870                ? deviceIndex
 0871                : 0;
 0872            var vendorOpts = string.IsNullOrEmpty(deviceVendorName)
 0873                ? ":0.0"
 0874                : ":." + deviceIndex + ",device_vendor=\"" + deviceVendorName + "\"";
 0875            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0876                ? vendorOpts
 0877                : "@" + srcDeviceAlias;
 878
 0879            return string.Format(
 0880                CultureInfo.InvariantCulture,
 0881                " -init_hw_device opencl={0}{1}",
 0882                alias,
 0883                options);
 884        }
 885
 886        private string GetD3d11vaDeviceArgs(int deviceIndex, string deviceVendorId, string alias)
 887        {
 0888            alias ??= D3d11vaAlias;
 0889            deviceIndex = deviceIndex >= 0 ? deviceIndex : 0;
 0890            var options = string.IsNullOrEmpty(deviceVendorId)
 0891                ? deviceIndex.ToString(CultureInfo.InvariantCulture)
 0892                : ",vendor=" + deviceVendorId;
 893
 0894            return string.Format(
 0895                CultureInfo.InvariantCulture,
 0896                " -init_hw_device d3d11va={0}:{1}",
 0897                alias,
 0898                options);
 899        }
 900
 901        private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string vendorId, st
 902        {
 0903            alias ??= VaapiAlias;
 0904            var haveVendorId = !string.IsNullOrEmpty(vendorId)
 0905                && _mediaEncoder.EncoderVersion >= _minFFmpegVaapiDeviceVendorId;
 906
 907            // Priority: 'renderNodePath' > 'vendorId' > 'kernelDriver'
 0908            var driverOpts = File.Exists(renderNodePath)
 0909                ? renderNodePath
 0910                : (haveVendorId ? $",vendor_id={vendorId}" : (string.IsNullOrEmpty(kernelDriver) ? string.Empty : $",ker
 911
 912            // 'driver' behaves similarly to env LIBVA_DRIVER_NAME
 0913            driverOpts += string.IsNullOrEmpty(driver) ? string.Empty : ",driver=" + driver;
 914
 0915            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0916                ? (string.IsNullOrEmpty(driverOpts) ? string.Empty : ":" + driverOpts)
 0917                : "@" + srcDeviceAlias;
 918
 0919            return string.Format(
 0920                CultureInfo.InvariantCulture,
 0921                " -init_hw_device vaapi={0}{1}",
 0922                alias,
 0923                options);
 924        }
 925
 926        private string GetDrmDeviceArgs(string renderNodePath, string alias)
 927        {
 0928            alias ??= DrmAlias;
 0929            renderNodePath = renderNodePath ?? "/dev/dri/renderD128";
 930
 0931            return string.Format(
 0932                CultureInfo.InvariantCulture,
 0933                " -init_hw_device drm={0}:{1}",
 0934                alias,
 0935                renderNodePath);
 936        }
 937
 938        private string GetQsvDeviceArgs(string renderNodePath, string alias)
 939        {
 0940            var arg = " -init_hw_device qsv=" + (alias ?? QsvAlias);
 0941            if (OperatingSystem.IsLinux())
 942            {
 943                // derive qsv from vaapi device
 0944                return GetVaapiDeviceArgs(renderNodePath, "iHD", "i915", "0x8086", null, VaapiAlias) + arg + "@" + Vaapi
 945            }
 946
 0947            if (OperatingSystem.IsWindows())
 948            {
 949                // on Windows, the deviceIndex is an int
 0950                if (int.TryParse(renderNodePath, NumberStyles.Integer, CultureInfo.InvariantCulture, out int deviceIndex
 951                {
 0952                    return GetD3d11vaDeviceArgs(deviceIndex, string.Empty, D3d11vaAlias) + arg + "@" + D3d11vaAlias;
 953                }
 954
 955                // derive qsv from d3d11va device
 0956                return GetD3d11vaDeviceArgs(0, "0x8086", D3d11vaAlias) + arg + "@" + D3d11vaAlias;
 957            }
 958
 0959            return null;
 960        }
 961
 962        private string GetFilterHwDeviceArgs(string alias)
 963        {
 0964            return string.IsNullOrEmpty(alias)
 0965                ? string.Empty
 0966                : " -filter_hw_device " + alias;
 967        }
 968
 969        public string GetGraphicalSubCanvasSize(EncodingJobInfo state)
 970        {
 971            // DVBSUB uses the fixed canvas size 720x576
 4972            if (state.SubtitleStream is not null
 4973                && ShouldEncodeSubtitle(state)
 4974                && !state.SubtitleStream.IsTextSubtitleStream
 4975                && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 976            {
 2977                var subtitleWidth = state.SubtitleStream?.Width;
 2978                var subtitleHeight = state.SubtitleStream?.Height;
 979
 2980                if (subtitleWidth.HasValue
 2981                    && subtitleHeight.HasValue
 2982                    && subtitleWidth.Value > 0
 2983                    && subtitleHeight.Value > 0)
 984                {
 0985                    return string.Format(
 0986                        CultureInfo.InvariantCulture,
 0987                        " -canvas_size {0}x{1}",
 0988                        subtitleWidth.Value,
 0989                        subtitleHeight.Value);
 990                }
 991            }
 992
 4993            return string.Empty;
 994        }
 995
 996        /// <summary>
 997        /// Gets the input video hwaccel argument.
 998        /// </summary>
 999        /// <param name="state">Encoding state.</param>
 1000        /// <param name="options">Encoding options.</param>
 1001        /// <returns>Input video hwaccel arguments.</returns>
 1002        public string GetInputVideoHwaccelArgs(EncodingJobInfo state, EncodingOptions options)
 1003        {
 41004            if (!state.IsVideoRequest)
 1005            {
 01006                return string.Empty;
 1007            }
 1008
 41009            var vidEncoder = GetVideoEncoder(state, options) ?? string.Empty;
 41010            if (IsCopyCodec(vidEncoder))
 1011            {
 41012                return string.Empty;
 1013            }
 1014
 01015            var args = new StringBuilder();
 01016            var isWindows = OperatingSystem.IsWindows();
 01017            var isLinux = OperatingSystem.IsLinux();
 01018            var isMacOS = OperatingSystem.IsMacOS();
 01019            var optHwaccelType = options.HardwareAccelerationType;
 01020            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 01021            var isHwTonemapAvailable = IsHwTonemapAvailable(state, options);
 1022
 01023            if (optHwaccelType == HardwareAccelerationType.vaapi)
 1024            {
 01025                if (!isLinux || !_mediaEncoder.SupportsHwaccel("vaapi"))
 1026                {
 01027                    return string.Empty;
 1028                }
 1029
 01030                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01031                var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01032                if (!isVaapiDecoder && !isVaapiEncoder)
 1033                {
 01034                    return string.Empty;
 1035                }
 1036
 01037                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 1038                {
 01039                    args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "iHD", null, null, null, VaapiAlias));
 1040                }
 01041                else if (_mediaEncoder.IsVaapiDeviceInteli965)
 1042                {
 1043                    // Only override i965 since it has lower priority than iHD in libva lookup.
 01044                    Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME", "i965");
 01045                    Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME_JELLYFIN", "i965");
 01046                    args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "i965", null, null, null, VaapiAlias));
 1047                }
 1048
 01049                var filterDevArgs = string.Empty;
 01050                var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
 1051
 01052                if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 1053                {
 01054                    if (doOclTonemap && !isVaapiDecoder)
 1055                    {
 01056                        args.Append(GetOpenclDeviceArgs(0, null, VaapiAlias, OpenclAlias));
 01057                        filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1058                    }
 1059                }
 01060                else if (_mediaEncoder.IsVaapiDeviceAmd)
 1061                {
 1062                    // Disable AMD EFC feature since it's still unstable in upstream Mesa.
 01063                    Environment.SetEnvironmentVariable("AMD_DEBUG", "noefc");
 1064
 01065                    if (IsVulkanFullSupported()
 01066                        && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 01067                        && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 1068                    {
 01069                        args.Append(GetDrmDeviceArgs(options.VaapiDevice, DrmAlias));
 01070                        args.Append(GetVaapiDeviceArgs(null, null, null, null, DrmAlias, VaapiAlias));
 01071                        args.Append(GetVulkanDeviceArgs(0, null, DrmAlias, VulkanAlias));
 1072
 1073                        // libplacebo wants an explicitly set vulkan filter device.
 01074                        filterDevArgs = GetFilterHwDeviceArgs(VulkanAlias);
 1075                    }
 1076                    else
 1077                    {
 01078                        args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, null, null, VaapiAlias));
 01079                        filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias);
 1080
 01081                        if (doOclTonemap)
 1082                        {
 1083                            // ROCm/ROCr OpenCL runtime
 01084                            args.Append(GetOpenclDeviceArgs(0, "Advanced Micro Devices", null, OpenclAlias));
 01085                            filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1086                        }
 1087                    }
 1088                }
 01089                else if (doOclTonemap)
 1090                {
 01091                    args.Append(GetOpenclDeviceArgs(0, null, null, OpenclAlias));
 01092                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1093                }
 1094
 01095                args.Append(filterDevArgs);
 1096            }
 01097            else if (optHwaccelType == HardwareAccelerationType.qsv)
 1098            {
 01099                if ((!isLinux && !isWindows) || !_mediaEncoder.SupportsHwaccel("qsv"))
 1100                {
 01101                    return string.Empty;
 1102                }
 1103
 01104                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 01105                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01106                var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 01107                var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 01108                var isHwDecoder = isQsvDecoder || isVaapiDecoder || isD3d11vaDecoder;
 01109                if (!isHwDecoder && !isQsvEncoder)
 1110                {
 01111                    return string.Empty;
 1112                }
 1113
 01114                args.Append(GetQsvDeviceArgs(options.QsvDevice, QsvAlias));
 01115                var filterDevArgs = GetFilterHwDeviceArgs(QsvAlias);
 1116                // child device used by qsv.
 01117                if (_mediaEncoder.SupportsHwaccel("vaapi") || _mediaEncoder.SupportsHwaccel("d3d11va"))
 1118                {
 01119                    if (isHwTonemapAvailable && IsOpenclFullSupported())
 1120                    {
 01121                        var srcAlias = isLinux ? VaapiAlias : D3d11vaAlias;
 01122                        args.Append(GetOpenclDeviceArgs(0, null, srcAlias, OpenclAlias));
 01123                        if (!isHwDecoder)
 1124                        {
 01125                            filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1126                        }
 1127                    }
 1128                }
 1129
 01130                args.Append(filterDevArgs);
 1131            }
 01132            else if (optHwaccelType == HardwareAccelerationType.nvenc)
 1133            {
 01134                if ((!isLinux && !isWindows) || !IsCudaFullSupported())
 1135                {
 01136                    return string.Empty;
 1137                }
 1138
 01139                var isCuvidDecoder = vidDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase);
 01140                var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 01141                var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 01142                var isHwDecoder = isNvdecDecoder || isCuvidDecoder;
 01143                if (!isHwDecoder && !isNvencEncoder)
 1144                {
 01145                    return string.Empty;
 1146                }
 1147
 01148                args.Append(GetCudaDeviceArgs(0, CudaAlias))
 01149                     .Append(GetFilterHwDeviceArgs(CudaAlias));
 1150            }
 01151            else if (optHwaccelType == HardwareAccelerationType.amf)
 1152            {
 01153                if (!isWindows || !_mediaEncoder.SupportsHwaccel("d3d11va"))
 1154                {
 01155                    return string.Empty;
 1156                }
 1157
 01158                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 01159                var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 01160                if (!isD3d11vaDecoder && !isAmfEncoder)
 1161                {
 01162                    return string.Empty;
 1163                }
 1164
 1165                // no dxva video processor hw filter.
 01166                args.Append(GetD3d11vaDeviceArgs(0, "0x1002", D3d11vaAlias));
 01167                var filterDevArgs = string.Empty;
 01168                if (IsOpenclFullSupported())
 1169                {
 01170                    args.Append(GetOpenclDeviceArgs(0, null, D3d11vaAlias, OpenclAlias));
 01171                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1172                }
 1173
 01174                args.Append(filterDevArgs);
 1175            }
 01176            else if (optHwaccelType == HardwareAccelerationType.videotoolbox)
 1177            {
 01178                if (!isMacOS || !_mediaEncoder.SupportsHwaccel("videotoolbox"))
 1179                {
 01180                    return string.Empty;
 1181                }
 1182
 01183                var isVideotoolboxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 01184                var isVideotoolboxEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 01185                if (!isVideotoolboxDecoder && !isVideotoolboxEncoder)
 1186                {
 01187                    return string.Empty;
 1188                }
 1189
 1190                // videotoolbox hw filter does not require device selection
 01191                args.Append(GetVideoToolboxDeviceArgs(VideotoolboxAlias));
 1192            }
 01193            else if (optHwaccelType == HardwareAccelerationType.rkmpp)
 1194            {
 01195                if (!isLinux || !_mediaEncoder.SupportsHwaccel("rkmpp"))
 1196                {
 01197                    return string.Empty;
 1198                }
 1199
 01200                var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 01201                var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 01202                if (!isRkmppDecoder && !isRkmppEncoder)
 1203                {
 01204                    return string.Empty;
 1205                }
 1206
 01207                args.Append(GetRkmppDeviceArgs(RkmppAlias));
 1208
 01209                var filterDevArgs = string.Empty;
 01210                var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
 1211
 01212                if (doOclTonemap && !isRkmppDecoder)
 1213                {
 01214                    args.Append(GetOpenclDeviceArgs(0, null, RkmppAlias, OpenclAlias));
 01215                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1216                }
 1217
 01218                args.Append(filterDevArgs);
 1219            }
 1220
 01221            if (!string.IsNullOrEmpty(vidDecoder))
 1222            {
 01223                args.Append(vidDecoder);
 1224            }
 1225
 01226            return args.ToString().Trim();
 1227        }
 1228
 1229        /// <summary>
 1230        /// Gets the input argument.
 1231        /// </summary>
 1232        /// <param name="state">Encoding state.</param>
 1233        /// <param name="options">Encoding options.</param>
 1234        /// <param name="segmentContainer">Segment Container.</param>
 1235        /// <returns>Input arguments.</returns>
 1236        public string GetInputArgument(EncodingJobInfo state, EncodingOptions options, string segmentContainer)
 1237        {
 41238            var arg = new StringBuilder();
 41239            var inputVidHwaccelArgs = GetInputVideoHwaccelArgs(state, options);
 1240
 41241            if (!string.IsNullOrEmpty(inputVidHwaccelArgs))
 1242            {
 01243                arg.Append(inputVidHwaccelArgs);
 1244            }
 1245
 41246            var canvasArgs = GetGraphicalSubCanvasSize(state);
 41247            if (!string.IsNullOrEmpty(canvasArgs))
 1248            {
 01249                arg.Append(canvasArgs);
 1250            }
 1251
 41252            if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
 1253            {
 01254                var concatFilePath = Path.Join(_configurationManager.CommonApplicationPaths.CachePath, "concat", state.M
 01255                if (!File.Exists(concatFilePath))
 1256                {
 01257                    _mediaEncoder.GenerateConcatConfig(state.MediaSource, concatFilePath);
 1258                }
 1259
 01260                arg.Append(" -f concat -safe 0 -i \"")
 01261                    .Append(concatFilePath)
 01262                    .Append("\" ");
 1263            }
 1264            else
 1265            {
 41266                arg.Append(" -i ")
 41267                    .Append(_mediaEncoder.GetInputPathArgument(state));
 1268            }
 1269
 41270            if (NeedsExternalSubtitleMuxing(state))
 1271            {
 41272                var subtitlePath = state.SubtitleStream.Path;
 41273                var isGraphicalBurnIn = ShouldEncodeSubtitle(state) && !state.SubtitleStream.IsTextSubtitleStream;
 1274
 1275                // dvdsub/vobsub graphical subtitles use .sub+.idx pairs
 41276                var subtitleExtension = Path.GetExtension(subtitlePath.AsSpan());
 41277                if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase))
 1278                {
 41279                    var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
 41280                    if (File.Exists(idxFile))
 1281                    {
 21282                        subtitlePath = idxFile;
 1283                    }
 1284                }
 1285
 1286                // Use analyzeduration also for subtitle streams to improve resolution detection  with streams inside MK
 41287                var analyzeDurationArgument = GetFfmpegAnalyzeDurationArg(state);
 41288                if (!string.IsNullOrEmpty(analyzeDurationArgument))
 1289                {
 01290                    arg.Append(' ').Append(analyzeDurationArgument);
 1291                }
 1292
 1293                // Apply probesize, too, if configured
 41294                var ffmpegProbeSizeArgument = GetFfmpegProbesizeArg();
 41295                if (!string.IsNullOrEmpty(ffmpegProbeSizeArgument))
 1296                {
 01297                    arg.Append(' ').Append(ffmpegProbeSizeArgument);
 1298                }
 1299
 1300                // Also seek the external subtitles stream.
 41301                var seekSubParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 41302                if (!string.IsNullOrEmpty(seekSubParam))
 1303                {
 01304                    arg.Append(' ').Append(seekSubParam);
 1305                }
 1306
 41307                if (isGraphicalBurnIn && !string.IsNullOrEmpty(canvasArgs))
 1308                {
 01309                    arg.Append(canvasArgs);
 1310                }
 1311
 41312                arg.Append(" -i file:\"").Append(subtitlePath).Append('\"');
 1313            }
 1314
 41315            if (state.AudioStream is not null && state.AudioStream.IsExternal)
 1316            {
 1317                // Also seek the external audio stream.
 01318                var seekAudioParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01319                if (!string.IsNullOrEmpty(seekAudioParam))
 1320                {
 01321                    arg.Append(' ').Append(seekAudioParam);
 1322                }
 1323
 01324                arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
 1325            }
 1326
 1327            // Disable auto inserted SW scaler for HW decoders in case of changed resolution.
 41328            var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
 41329            if (!isSwDecoder)
 1330            {
 01331                arg.Append(" -noautoscale");
 1332            }
 1333
 41334            return arg.ToString();
 1335        }
 1336
 1337        /// <summary>
 1338        /// Determines whether the specified stream is H264.
 1339        /// </summary>
 1340        /// <param name="stream">The stream.</param>
 1341        /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
 1342        public static bool IsH264(MediaStream stream)
 1343        {
 01344            var codec = stream.Codec ?? string.Empty;
 1345
 01346            return codec.Contains("264", StringComparison.OrdinalIgnoreCase)
 01347                    || codec.Contains("avc", StringComparison.OrdinalIgnoreCase);
 1348        }
 1349
 1350        public static bool IsH265(MediaStream stream)
 1351        {
 01352            var codec = stream.Codec ?? string.Empty;
 1353
 01354            return codec.Contains("265", StringComparison.OrdinalIgnoreCase)
 01355                || codec.Contains("hevc", StringComparison.OrdinalIgnoreCase);
 1356        }
 1357
 1358        public static bool IsAv1(MediaStream stream)
 1359        {
 01360            var codec = stream.Codec ?? string.Empty;
 1361
 01362            return codec.Contains("av1", StringComparison.OrdinalIgnoreCase);
 1363        }
 1364
 1365        public static bool IsAAC(MediaStream stream)
 1366        {
 01367            var codec = stream.Codec ?? string.Empty;
 1368
 01369            return codec.Contains("aac", StringComparison.OrdinalIgnoreCase);
 1370        }
 1371
 1372        public static bool IsDoviWithHdr10Bl(MediaStream stream)
 1373        {
 01374            var rangeType = stream?.VideoRangeType;
 1375
 01376            return rangeType is VideoRangeType.DOVIWithHDR10
 01377                or VideoRangeType.DOVIWithEL
 01378                or VideoRangeType.DOVIWithHDR10Plus
 01379                or VideoRangeType.DOVIWithELHDR10Plus
 01380                or VideoRangeType.DOVIInvalid;
 1381        }
 1382
 1383        public static bool IsDovi(MediaStream stream)
 1384        {
 01385            var rangeType = stream?.VideoRangeType;
 1386
 01387            return IsDoviWithHdr10Bl(stream)
 01388                   || (rangeType is VideoRangeType.DOVI
 01389                       or VideoRangeType.DOVIWithHLG
 01390                       or VideoRangeType.DOVIWithSDR);
 1391        }
 1392
 1393        public static bool IsHdr10Plus(MediaStream stream)
 1394        {
 01395            var rangeType = stream?.VideoRangeType;
 1396
 01397            return rangeType is VideoRangeType.HDR10Plus
 01398                       or VideoRangeType.DOVIWithHDR10Plus
 01399                       or VideoRangeType.DOVIWithELHDR10Plus;
 1400        }
 1401
 1402        /// <summary>
 1403        /// Check if dynamic HDR metadata should be removed during stream copy.
 1404        /// Please note this check assumes the range check has already been done
 1405        /// and trivial fallbacks like HDR10+ to HDR10, DOVIWithHDR10 to HDR10 is already checked.
 1406        /// </summary>
 1407        private static DynamicHdrMetadataRemovalPlan ShouldRemoveDynamicHdrMetadata(EncodingJobInfo state)
 1408        {
 01409            var videoStream = state.VideoStream;
 01410            if (videoStream.VideoRange is not VideoRange.HDR)
 1411            {
 01412                return DynamicHdrMetadataRemovalPlan.None;
 1413            }
 1414
 01415            var requestedRangeTypes = state.GetRequestedRangeTypes(state.VideoStream.Codec);
 01416            if (requestedRangeTypes.Length == 0)
 1417            {
 01418                return DynamicHdrMetadataRemovalPlan.None;
 1419            }
 1420
 01421            var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ordinal
 01422            var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIg
 01423            var requestHasDOVIwithEL = requestedRangeTypes.Contains(VideoRangeType.DOVIWithEL.ToString(), StringComparis
 01424            var requestHasDOVIwithELHDR10plus = requestedRangeTypes.Contains(VideoRangeType.DOVIWithELHDR10Plus.ToString
 1425
 01426            var shouldRemoveHdr10Plus = false;
 1427            // Case 1: Client supports HDR10, does not support DOVI with EL but EL presets
 01428            var shouldRemoveDovi = (!requestHasDOVIwithEL && requestHasHDR10) && videoStream.VideoRangeType == VideoRang
 1429
 1430            // Case 2: Client supports DOVI, does not support broken DOVI config
 1431            // Client does not report DOVI support should be allowed to copy bad data for remuxing as HDR10 players woul
 01432            shouldRemoveDovi = shouldRemoveDovi || (requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVII
 1433
 1434            // Special case: we have a video with both EL and HDR10+
 1435            // If the client supports EL but not in the case of coexistence with HDR10+, remove HDR10+ for compatibility
 1436            // Otherwise, remove DOVI if the client is not a DOVI player
 01437            if (videoStream.VideoRangeType == VideoRangeType.DOVIWithELHDR10Plus)
 1438            {
 01439                shouldRemoveHdr10Plus = requestHasDOVIwithEL && !requestHasDOVIwithELHDR10plus;
 01440                shouldRemoveDovi = shouldRemoveDovi || !shouldRemoveHdr10Plus;
 1441            }
 1442
 01443            if (shouldRemoveDovi)
 1444            {
 01445                return DynamicHdrMetadataRemovalPlan.RemoveDovi;
 1446            }
 1447
 1448            // If the client is a Dolby Vision Player, remove the HDR10+ metadata to avoid playback issues
 01449            shouldRemoveHdr10Plus = shouldRemoveHdr10Plus || (requestHasDOVI && videoStream.VideoRangeType == VideoRange
 01450            return shouldRemoveHdr10Plus ? DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus : DynamicHdrMetadataRemovalPlan
 1451        }
 1452
 1453        private bool CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan plan, MediaStream videoStream)
 1454        {
 01455            return plan switch
 01456            {
 01457                DynamicHdrMetadataRemovalPlan.RemoveDovi => _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFil
 01458                                                            || (IsH265(videoStream) && _mediaEncoder.SupportsBitStreamFi
 01459                                                            || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStreamFil
 01460                DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus => (IsH265(videoStream) && _mediaEncoder.SupportsBitStream
 01461                                                                 || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStre
 01462                _ => true,
 01463            };
 1464        }
 1465
 1466        public bool IsDoviRemoved(EncodingJobInfo state)
 1467        {
 01468            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01469                                              && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Remove
 1470        }
 1471
 1472        public bool IsHdr10PlusRemoved(EncodingJobInfo state)
 1473        {
 01474            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01475                                                  && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Re
 1476        }
 1477
 1478        public string GetBitStreamArgs(EncodingJobInfo state, MediaStreamType streamType)
 1479        {
 01480            if (state is null)
 1481            {
 01482                return null;
 1483            }
 1484
 01485            var stream = streamType switch
 01486            {
 01487                MediaStreamType.Audio => state.AudioStream,
 01488                MediaStreamType.Video => state.VideoStream,
 01489                _ => state.VideoStream
 01490            };
 1491            // TODO This is auto inserted into the mpegts mux so it might not be needed.
 1492            // https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
 01493            if (IsH264(stream))
 1494            {
 01495                return "-bsf:v h264_mp4toannexb";
 1496            }
 1497
 01498            if (IsAAC(stream))
 1499            {
 1500                // Convert adts header(mpegts) to asc header(mp4).
 01501                return "-bsf:a aac_adtstoasc";
 1502            }
 1503
 01504            if (IsH265(stream))
 1505            {
 01506                var filter = "-bsf:v hevc_mp4toannexb";
 1507
 1508                // The following checks are not complete because the copy would be rejected
 1509                // if the encoder cannot remove required metadata.
 1510                // And if bsf is used, we must already be using copy codec.
 01511                switch (ShouldRemoveDynamicHdrMetadata(state))
 1512                {
 1513                    default:
 1514                    case DynamicHdrMetadataRemovalPlan.None:
 1515                        break;
 1516                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01517                        filter += _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.HevcMetadata
 01518                            ? ",hevc_metadata=remove_dovi=1"
 01519                            : ",dovi_rpu=strip=1";
 01520                        break;
 1521                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01522                        filter += ",hevc_metadata=remove_hdr10plus=1";
 1523                        break;
 1524                }
 1525
 01526                return filter;
 1527            }
 1528
 01529            if (IsAv1(stream))
 1530            {
 01531                switch (ShouldRemoveDynamicHdrMetadata(state))
 1532                {
 1533                    default:
 1534                    case DynamicHdrMetadataRemovalPlan.None:
 01535                        return null;
 1536                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01537                        return _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.Av1MetadataRemo
 01538                            ? "-bsf:v av1_metadata=remove_dovi=1"
 01539                            : "-bsf:v dovi_rpu=strip=1";
 1540                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01541                        return "-bsf:v av1_metadata=remove_hdr10plus=1";
 1542                }
 1543            }
 1544
 01545            return null;
 1546        }
 1547
 1548        public string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceConta
 1549        {
 01550            var bitStreamArgs = string.Empty;
 01551            var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
 1552
 1553            // Apply aac_adtstoasc bitstream filter when media source is in mpegts.
 01554            if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)
 01555                && (string.Equals(mediaSourceContainer, "ts", StringComparison.OrdinalIgnoreCase)
 01556                    || string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase)
 01557                    || string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase)))
 1558            {
 01559                bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Audio);
 01560                bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs;
 1561            }
 1562
 01563            return bitStreamArgs;
 1564        }
 1565
 1566        public static string GetSegmentFileExtension(string segmentContainer)
 1567        {
 01568            if (!string.IsNullOrWhiteSpace(segmentContainer))
 1569            {
 01570                return "." + segmentContainer;
 1571            }
 1572
 01573            return ".ts";
 1574        }
 1575
 1576        private string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
 1577        {
 01578            if (state.OutputVideoBitrate is null)
 1579            {
 01580                return string.Empty;
 1581            }
 1582
 01583            int bitrate = state.OutputVideoBitrate.Value;
 1584
 1585            // Bit rate under 1000k is not allowed in h264_qsv.
 01586            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 1587            {
 01588                bitrate = Math.Max(bitrate, 1000);
 1589            }
 1590
 1591            // Currently use the same buffer size for all non-QSV encoders.
 1592            // Use long arithmetic to prevent int32 overflow for very high bitrate values.
 01593            int bufsize = (int)Math.Min((long)bitrate * 2, int.MaxValue);
 1594
 01595            if (string.Equals(videoCodec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1596            {
 01597                return FormattableString.Invariant($" -b:v {bitrate} -bufsize {bufsize}");
 1598            }
 1599
 01600            if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)
 01601                || string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
 1602            {
 01603                return FormattableString.Invariant($" -maxrate {bitrate} -bufsize {bufsize}");
 1604            }
 1605
 01606            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01607                || string.Equals(videoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 01608                || string.Equals(videoCodec, "av1_qsv", StringComparison.OrdinalIgnoreCase))
 1609            {
 1610                // TODO: probe QSV encoders' capabilities and enable more tuning options
 1611                // See also https://github.com/intel/media-delivery/blob/master/doc/quality.rst
 1612
 1613                // Enable MacroBlock level bitrate control for better subjective visual quality
 01614                var mbbrcOpt = string.Empty;
 01615                if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01616                    || string.Equals(videoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 1617                {
 01618                    mbbrcOpt = " -mbbrc 1";
 1619                }
 1620
 1621                // Some less powerful H.264 HW decoders require strict CPB size
 1622                // So bufsize optimizations should not be applied to them
 01623                int factor = 2;
 01624                var codec = state.ActualOutputVideoCodec;
 01625                var level = state.GetRequestedLevel(codec);
 01626                if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)
 01627                    && double.TryParse(level, CultureInfo.InvariantCulture, out double requestedLevel)
 01628                    && requestedLevel < 51)
 1629                {
 01630                    factor = 1;
 1631                }
 1632
 1633                // Set (maxrate == bitrate + 1) to trigger VBR for better bitrate allocation
 1634                // Set (rc_init_occupancy == 2 * bitrate) and (bufsize == 4 * bitrate) to deal with drastic scene change
 1635                // Use long arithmetic and clamp to int.MaxValue to prevent int32 overflow
 1636                // (e.g. bitrate * 4 wraps to a negative value for bitrates above ~537 million)
 01637                int qsvMaxrate = (int)Math.Min((long)bitrate + 1, int.MaxValue);
 01638                int qsvInitOcc = (int)Math.Min((long)bitrate * 1 * factor, int.MaxValue);
 01639                int qsvBufsize = (int)Math.Min((long)bitrate * 2 * factor, int.MaxValue);
 1640
 01641                return FormattableString.Invariant($"{mbbrcOpt} -b:v {bitrate} -maxrate {qsvMaxrate} -rc_init_occupancy 
 1642            }
 1643
 01644            if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01645                || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase))
 1646            {
 1647                // Override the too high default qmin 18 in transcoding preset in legacy h26x_amf
 01648                return FormattableString.Invariant($" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsiz
 1649            }
 1650
 01651            if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01652                || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01653                || string.Equals(videoCodec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1654            {
 1655                // VBR in i965 driver may result in pixelated output.
 01656                if (_mediaEncoder.IsVaapiDeviceInteli965)
 1657                {
 01658                    return FormattableString.Invariant($" -rc_mode CBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsi
 1659                }
 1660
 01661                return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"
 1662            }
 1663
 01664            if (string.Equals(videoCodec, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 01665                || string.Equals(videoCodec, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase))
 1666            {
 1667                // The `maxrate` and `bufsize` options can potentially lead to performance regression
 1668                // and even encoder hangs, especially when the value is very high.
 01669                return FormattableString.Invariant($" -b:v {bitrate} -qmin -1 -qmax -1");
 1670            }
 1671
 01672            return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
 1673        }
 1674
 1675        private string GetEncoderParam(EncoderPreset? preset, EncoderPreset defaultPreset, EncodingOptions encodingOptio
 1676        {
 01677            var param = string.Empty;
 01678            var encoderPreset = preset ?? defaultPreset;
 01679            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
 1680            {
 01681                var presetString = encoderPreset switch
 01682                {
 01683                    EncoderPreset.auto => EncoderPreset.veryfast.ToString().ToLowerInvariant(),
 01684                    _ => encoderPreset.ToString().ToLowerInvariant()
 01685                };
 1686
 01687                param += " -preset " + presetString;
 1688
 01689                int encodeCrf = encodingOptions.H264Crf;
 01690                if (isLibX265)
 1691                {
 01692                    encodeCrf = encodingOptions.H265Crf;
 1693                }
 1694
 01695                if (encodeCrf >= 0 && encodeCrf <= 51)
 1696                {
 01697                    param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture);
 1698                }
 1699                else
 1700                {
 01701                    string defaultCrf = "23";
 01702                    if (isLibX265)
 1703                    {
 01704                        defaultCrf = "28";
 1705                    }
 1706
 01707                    param += " -crf " + defaultCrf;
 1708                }
 1709            }
 01710            else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1711            {
 1712                // Default to use the recommended preset 10.
 1713                // Omit presets < 5, which are too slow for on the fly encoding.
 1714                // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md
 01715                param += encoderPreset switch
 01716                {
 01717                    EncoderPreset.veryslow => " -preset 5",
 01718                    EncoderPreset.slower => " -preset 6",
 01719                    EncoderPreset.slow => " -preset 7",
 01720                    EncoderPreset.medium => " -preset 8",
 01721                    EncoderPreset.fast => " -preset 9",
 01722                    EncoderPreset.faster => " -preset 10",
 01723                    EncoderPreset.veryfast => " -preset 11",
 01724                    EncoderPreset.superfast => " -preset 12",
 01725                    EncoderPreset.ultrafast => " -preset 13",
 01726                    _ => " -preset 10"
 01727                };
 1728            }
 01729            else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01730                     || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01731                     || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1732            {
 1733                // -compression_level is not reliable on AMD.
 01734                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 1735                {
 01736                    param += encoderPreset switch
 01737                    {
 01738                        EncoderPreset.veryslow => " -compression_level 1",
 01739                        EncoderPreset.slower => " -compression_level 2",
 01740                        EncoderPreset.slow => " -compression_level 3",
 01741                        EncoderPreset.medium => " -compression_level 4",
 01742                        EncoderPreset.fast => " -compression_level 5",
 01743                        EncoderPreset.faster => " -compression_level 6",
 01744                        EncoderPreset.veryfast => " -compression_level 7",
 01745                        EncoderPreset.superfast => " -compression_level 7",
 01746                        EncoderPreset.ultrafast => " -compression_level 7",
 01747                        _ => string.Empty
 01748                    };
 1749                }
 1750            }
 01751            else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv)
 01752                     || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv)
 01753                     || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv)
 1754            {
 01755                EncoderPreset[] valid_presets = [EncoderPreset.veryslow, EncoderPreset.slower, EncoderPreset.slow, Encod
 1756
 01757                param += " -preset " + (valid_presets.Contains(encoderPreset) ? encoderPreset : EncoderPreset.veryfast).
 1758            }
 01759            else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
 01760                        || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_n
 01761                        || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase) // av1 (av1_nven
 01762            )
 1763            {
 01764                param += encoderPreset switch
 01765                {
 01766                    EncoderPreset.veryslow => " -preset p7",
 01767                    EncoderPreset.slower => " -preset p6",
 01768                    EncoderPreset.slow => " -preset p5",
 01769                    EncoderPreset.medium => " -preset p4",
 01770                    EncoderPreset.fast => " -preset p3",
 01771                    EncoderPreset.faster => " -preset p2",
 01772                    _ => " -preset p1"
 01773                };
 1774            }
 01775            else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf)
 01776                        || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf
 01777                        || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase) // av1 (av1_amf)
 01778            )
 1779            {
 01780                param += encoderPreset switch
 01781                {
 01782                    EncoderPreset.veryslow => " -quality quality",
 01783                    EncoderPreset.slower => " -quality quality",
 01784                    EncoderPreset.slow => " -quality quality",
 01785                    EncoderPreset.medium => " -quality balanced",
 01786                    _ => " -quality speed"
 01787                };
 1788
 01789                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01790                    || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1791                {
 01792                    param += " -header_insertion_mode gop";
 1793                }
 1794
 01795                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
 1796                {
 01797                    param += " -gops_per_idr 1";
 1798                }
 1799            }
 01800            else if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase) // h264 (h264_
 01801                        || string.Equals(videoEncoder, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase) // hevc 
 01802            )
 1803            {
 01804                param += encoderPreset switch
 01805                {
 01806                    EncoderPreset.veryslow => " -prio_speed 0",
 01807                    EncoderPreset.slower => " -prio_speed 0",
 01808                    EncoderPreset.slow => " -prio_speed 0",
 01809                    EncoderPreset.medium => " -prio_speed 0",
 01810                    _ => " -prio_speed 1"
 01811                };
 1812            }
 1813
 01814            return param;
 1815        }
 1816
 1817        public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
 1818        {
 01819            if (!double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel))
 1820            {
 01821                return null;
 1822            }
 1823
 01824            if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase))
 1825            {
 1826                // Transcode to level 5.3 (15) and lower for maximum compatibility.
 1827                // https://en.wikipedia.org/wiki/AV1#Levels
 01828                if (requestLevel < 0 || requestLevel >= 15)
 1829                {
 01830                    return "15";
 1831                }
 1832            }
 01833            else if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 01834                     || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
 1835            {
 1836                // Transcode to level 5.0 and lower for maximum compatibility.
 1837                // Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it.
 1838                // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
 1839                // MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880.
 01840                if (requestLevel < 0 || requestLevel >= 150)
 1841                {
 01842                    return "150";
 1843                }
 1844            }
 01845            else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
 1846            {
 1847                // Transcode to level 5.1 and lower for maximum compatibility.
 1848                // h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
 1849                // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
 01850                if (requestLevel < 0 || requestLevel >= 51)
 1851                {
 01852                    return "51";
 1853                }
 1854            }
 1855
 01856            return level;
 1857        }
 1858
 1859        /// <summary>
 1860        /// Gets the text subtitle param.
 1861        /// </summary>
 1862        /// <param name="state">The state.</param>
 1863        /// <param name="enableAlpha">Enable alpha processing.</param>
 1864        /// <param name="enableSub2video">Enable sub2video mode.</param>
 1865        /// <returns>System.String.</returns>
 1866        public string GetTextSubtitlesFilter(EncodingJobInfo state, bool enableAlpha, bool enableSub2video)
 1867        {
 01868            var seconds = Math.Round(TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds);
 1869
 1870            // hls always copies timestamps
 01871            var setPtsParam = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive
 01872                ? string.Empty
 01873                : string.Format(CultureInfo.InvariantCulture, ",setpts=PTS -{0}/TB", seconds);
 1874
 01875            var alphaParam = enableAlpha ? ":alpha=1" : string.Empty;
 01876            var sub2videoParam = enableSub2video ? ":sub2video=1" : string.Empty;
 1877
 01878            var fontPath = _pathManager.GetAttachmentFolderPath(state.MediaSource.Id);
 01879            var fontParam = fontPath is null
 01880                ? string.Empty
 01881                : string.Format(
 01882                    CultureInfo.InvariantCulture,
 01883                    ":fontsdir='{0}'",
 01884                    _mediaEncoder.EscapeSubtitleFilterPath(fontPath));
 1885
 01886            if (state.SubtitleStream.IsExternal)
 1887            {
 01888                var charsetParam = string.Empty;
 1889
 01890                if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
 1891                {
 01892                    var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
 01893                            state.SubtitleStream,
 01894                            state.SubtitleStream.Language,
 01895                            state.MediaSource,
 01896                            CancellationToken.None).GetAwaiter().GetResult();
 1897
 01898                    if (!string.IsNullOrEmpty(charenc))
 1899                    {
 01900                        charsetParam = ":charenc=" + charenc;
 1901                    }
 1902                }
 1903
 01904                return string.Format(
 01905                    CultureInfo.InvariantCulture,
 01906                    "subtitles=f='{0}'{1}{2}{3}{4}{5}",
 01907                    _mediaEncoder.EscapeSubtitleFilterPath(state.SubtitleStream.Path),
 01908                    charsetParam,
 01909                    alphaParam,
 01910                    sub2videoParam,
 01911                    fontParam,
 01912                    setPtsParam);
 1913            }
 1914
 01915            var subtitlePath = _subtitleEncoder.GetSubtitleFilePath(
 01916                    state.SubtitleStream,
 01917                    state.MediaSource,
 01918                    CancellationToken.None).GetAwaiter().GetResult();
 1919
 01920            return string.Format(
 01921                CultureInfo.InvariantCulture,
 01922                "subtitles=f='{0}'{1}{2}{3}{4}",
 01923                _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
 01924                alphaParam,
 01925                sub2videoParam,
 01926                fontParam,
 01927                setPtsParam);
 1928        }
 1929
 1930        public double? GetFramerateParam(EncodingJobInfo state)
 1931        {
 01932            var request = state.BaseRequest;
 1933
 01934            if (request.Framerate.HasValue)
 1935            {
 01936                return request.Framerate.Value;
 1937            }
 1938
 01939            var maxrate = request.MaxFramerate;
 1940
 01941            if (maxrate.HasValue && state.VideoStream is not null)
 1942            {
 01943                var contentRate = state.VideoStream.ReferenceFrameRate;
 1944
 01945                if (contentRate.HasValue && contentRate.Value > maxrate.Value)
 1946                {
 01947                    return maxrate;
 1948                }
 1949            }
 1950
 01951            return null;
 1952        }
 1953
 1954        public string GetHlsVideoKeyFrameArguments(
 1955            EncodingJobInfo state,
 1956            string codec,
 1957            int segmentLength,
 1958            bool isEventPlaylist,
 1959            int? startNumber)
 1960        {
 01961            var args = string.Empty;
 01962            var gopArg = string.Empty;
 1963
 01964            var keyFrameArg = string.Format(
 01965                CultureInfo.InvariantCulture,
 01966                " -force_key_frames:0 \"expr:gte(t,n_forced*{0})\"",
 01967                segmentLength);
 1968
 01969            var framerate = state.VideoStream?.RealFrameRate;
 01970            if (framerate.HasValue)
 1971            {
 1972                // This is to make sure keyframe interval is limited to our segment,
 1973                // as forcing keyframes is not enough.
 1974                // Example: we encoded half of desired length, then codec detected
 1975                // scene cut and inserted a keyframe; next forced keyframe would
 1976                // be created outside of segment, which breaks seeking.
 01977                gopArg = string.Format(
 01978                    CultureInfo.InvariantCulture,
 01979                    " -g:v:0 {0} -keyint_min:v:0 {0}",
 01980                    Math.Ceiling(segmentLength * framerate.Value));
 1981            }
 1982
 1983            // Unable to force key frames using these encoders, set key frames by GOP.
 01984            if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01985                || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 01986                || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01987                || string.Equals(codec, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 01988                || string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 01989                || string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 01990                || string.Equals(codec, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase)
 01991                || string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 01992                || string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 01993                || string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase)
 01994                || string.Equals(codec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1995            {
 01996                args += gopArg;
 1997            }
 01998            else if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase)
 01999                     || string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)
 02000                     || string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02001                     || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02002                     || string.Equals(codec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 2003            {
 02004                args += keyFrameArg;
 2005
 2006                // prevent the libx264 from post processing to break the set keyframe.
 02007                if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase))
 2008                {
 02009                    args += " -sc_threshold:v:0 0";
 2010                }
 2011            }
 2012            else
 2013            {
 02014                args += keyFrameArg + gopArg;
 2015            }
 2016
 2017            // global_header produced by AMD HEVC VA-API encoder causes non-playable fMP4 on iOS
 02018            if (string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02019                && _mediaEncoder.IsVaapiDeviceAmd)
 2020            {
 02021                args += " -flags:v -global_header";
 2022            }
 2023
 02024            return args;
 2025        }
 2026
 2027        /// <summary>
 2028        /// Gets the video bitrate to specify on the command line.
 2029        /// </summary>
 2030        /// <param name="state">Encoding state.</param>
 2031        /// <param name="videoEncoder">Video encoder to use.</param>
 2032        /// <param name="encodingOptions">Encoding options.</param>
 2033        /// <param name="defaultPreset">Default present to use for encoding.</param>
 2034        /// <returns>Video bitrate.</returns>
 2035        public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, 
 2036        {
 02037            var param = string.Empty;
 2038
 2039            // Tutorials: Enable Intel GuC / HuC firmware loading for Low Power Encoding.
 2040            // https://01.org/group/43/downloads/firmware
 2041            // https://wiki.archlinux.org/title/intel_graphics#Enable_GuC_/_HuC_firmware_loading
 2042            // Intel Low Power Encoding can save unnecessary CPU-GPU synchronization,
 2043            // which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
 02044            var intelLowPowerHwEncoding = false;
 2045
 2046            // Workaround for linux 5.18 to 6.1.3 i915 hang at cost of performance.
 2047            // https://github.com/intel/media-driver/issues/1456
 02048            var enableWaFori915Hang = false;
 2049
 02050            var hardwareAccelerationType = encodingOptions.HardwareAccelerationType;
 2051
 02052            if (hardwareAccelerationType == HardwareAccelerationType.vaapi)
 2053            {
 02054                var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
 2055
 02056                if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
 2057                {
 02058                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder && isIntelVaapiDriver;
 2059                }
 02060                else if (string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
 2061                {
 02062                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder && isIntelVaapiDriver;
 2063                }
 2064            }
 02065            else if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 2066            {
 02067                if (OperatingSystem.IsLinux())
 2068                {
 02069                    var ver = Environment.OSVersion.Version;
 02070                    var isFixedKernel60 = ver.Major == 6 && ver.Minor == 0 && ver >= _minFixedKernel60i915Hang;
 02071                    var isUnaffectedKernel = ver < _minKerneli915Hang || ver > _maxKerneli915Hang;
 2072
 02073                    if (!(isUnaffectedKernel || isFixedKernel60))
 2074                    {
 02075                        var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
 02076                        var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
 02077                                             || vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 02078                        var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
 02079                            && IsVaapiSupported(state)
 02080                            && IsOpenclFullSupported()
 02081                            && !IsIntelVppTonemapAvailable(state, encodingOptions)
 02082                            && IsHwTonemapAvailable(state, encodingOptions);
 2083
 02084                        enableWaFori915Hang = isIntelDecoder && doOclTonemap;
 2085                    }
 2086                }
 2087
 02088                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 2089                {
 02090                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder;
 2091                }
 02092                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2093                {
 02094                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder;
 2095                }
 2096                else
 2097                {
 02098                    enableWaFori915Hang = false;
 2099                }
 2100            }
 2101
 02102            if (intelLowPowerHwEncoding)
 2103            {
 02104                param += " -low_power 1";
 2105            }
 2106
 02107            if (enableWaFori915Hang)
 2108            {
 02109                param += " -async_depth 1";
 2110            }
 2111
 02112            var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
 02113            var encodingPreset = encodingOptions.EncoderPreset;
 2114
 02115            param += GetEncoderParam(encodingPreset, defaultPreset, encodingOptions, videoEncoder, isLibX265);
 02116            param += GetVideoBitrateParam(state, videoEncoder);
 2117
 02118            var framerate = GetFramerateParam(state);
 02119            if (framerate.HasValue)
 2120            {
 02121                param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(CultureInfo.Inv
 2122            }
 2123
 02124            var targetVideoCodec = state.ActualOutputVideoCodec;
 02125            if (string.Equals(targetVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
 02126                || string.Equals(targetVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
 2127            {
 02128                targetVideoCodec = "hevc";
 2129            }
 2130
 02131            var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
 02132            profile = WhiteSpaceRegex().Replace(profile, string.Empty).ToLowerInvariant();
 2133
 02134            var videoProfiles = Array.Empty<string>();
 02135            if (string.Equals("h264", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2136            {
 02137                videoProfiles = _videoProfilesH264;
 2138            }
 02139            else if (string.Equals("hevc", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2140            {
 02141                videoProfiles = _videoProfilesH265;
 2142            }
 02143            else if (string.Equals("av1", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2144            {
 02145                videoProfiles = _videoProfilesAv1;
 2146            }
 2147
 02148            if (!videoProfiles.Contains(profile, StringComparison.OrdinalIgnoreCase))
 2149            {
 02150                profile = string.Empty;
 2151            }
 2152
 2153            // We only transcode to HEVC 8-bit for now, force Main Profile.
 02154            if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)
 02155                || profile.Contains("mainstill", StringComparison.OrdinalIgnoreCase))
 2156            {
 02157                profile = "main";
 2158            }
 2159
 2160            // Extended Profile is not supported by any known h264 encoders, force Main Profile.
 02161            if (profile.Contains("extended", StringComparison.OrdinalIgnoreCase))
 2162            {
 02163                profile = "main";
 2164            }
 2165
 2166            // Only libx264 support encoding H264 High 10 Profile, otherwise force High Profile.
 02167            if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02168                && profile.Contains("high10", StringComparison.OrdinalIgnoreCase))
 2169            {
 02170                profile = "high";
 2171            }
 2172
 2173            // We only need Main profile of AV1 encoders.
 02174            if (videoEncoder.Contains("av1", StringComparison.OrdinalIgnoreCase)
 02175                && (profile.Contains("high", StringComparison.OrdinalIgnoreCase)
 02176                    || profile.Contains("professional", StringComparison.OrdinalIgnoreCase)))
 2177            {
 02178                profile = "main";
 2179            }
 2180
 2181            // h264_vaapi does not support Baseline profile, force Constrained Baseline in this case,
 2182            // which is compatible (and ugly).
 02183            if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02184                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2185            {
 02186                profile = "constrained_baseline";
 2187            }
 2188
 2189            // libx264, h264_{qsv,nvenc,rkmpp} does not support Constrained Baseline profile, force Baseline in this cas
 02190            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02191                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02192                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02193                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02194                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2195            {
 02196                profile = "baseline";
 2197            }
 2198
 2199            // libx264, h264_{qsv,nvenc,vaapi,rkmpp} does not support Constrained High profile, force High in this case.
 02200            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02201                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02202                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02203                 || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02204                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02205                && profile.Contains("high", StringComparison.OrdinalIgnoreCase))
 2206            {
 02207                profile = "high";
 2208            }
 2209
 02210            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02211                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2212            {
 02213                profile = "constrained_baseline";
 2214            }
 2215
 02216            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02217                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2218            {
 02219                profile = "constrained_high";
 2220            }
 2221
 02222            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02223                && profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase))
 2224            {
 02225                profile = "constrained_baseline";
 2226            }
 2227
 02228            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02229                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2230            {
 02231                profile = "constrained_high";
 2232            }
 2233
 02234            if (!string.IsNullOrEmpty(profile))
 2235            {
 2236                // Currently there's no profile option in av1_nvenc encoder
 02237                if (!(string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 02238                      || string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)))
 2239                {
 02240                    param += " -profile:v:0 " + profile;
 2241                }
 2242            }
 2243
 02244            var level = NormalizeTranscodingLevel(state, state.GetRequestedLevel(targetVideoCodec));
 2245
 02246            if (!string.IsNullOrEmpty(level))
 2247            {
 2248                // libx264, QSV, AMF can adjust the given level to match the output.
 02249                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02250                    || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2251                {
 02252                    param += " -level " + level;
 2253                }
 02254                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2255                {
 2256                    // hevc_qsv use -level 51 instead of -level 153.
 02257                    if (double.TryParse(level, CultureInfo.InvariantCulture, out double hevcLevel))
 2258                    {
 02259                        param += " -level " + (hevcLevel / 3);
 2260                    }
 2261                }
 02262                else if (string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 02263                         || string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 2264                {
 2265                    // libsvtav1 and av1_qsv use -level 60 instead of -level 16
 2266                    // https://aomedia.org/av1/specification/annex-a/
 02267                    if (int.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out int av1Level))
 2268                    {
 02269                        var x = 2 + (av1Level >> 2);
 02270                        var y = av1Level & 3;
 02271                        var res = (x * 10) + y;
 02272                        param += " -level " + res;
 2273                    }
 2274                }
 02275                else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02276                         || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 02277                         || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 2278                {
 02279                    param += " -level " + level;
 2280                }
 02281                else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02282                         || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 02283                         || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase))
 2284                {
 2285                    // level option may cause NVENC to fail.
 2286                    // NVENC cannot adjust the given level, just throw an error.
 2287                }
 02288                else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02289                         || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02290                         || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 2291                {
 2292                    // level option may cause corrupted frames on AMD VAAPI.
 02293                    if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 2294                    {
 02295                        param += " -level " + level;
 2296                    }
 2297                }
 02298                else if (string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 02299                         || string.Equals(videoEncoder, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase))
 2300                {
 02301                    param += " -level " + level;
 2302                }
 02303                else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2304                {
 02305                    param += " -level " + level;
 2306                }
 2307            }
 2308
 02309            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2310            {
 02311                param += " -x264opts:0 subme=0:me_range=16:rc_lookahead=10:me=hex:open_gop=0";
 2312            }
 2313
 02314            if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2315            {
 2316                // libx265 only accept level option in -x265-params.
 2317                // level option may cause libx265 to fail.
 2318                // libx265 cannot adjust the given level, just throw an error.
 02319                param += " -x265-params:0 no-scenecut=1:no-open-gop=1:no-info=1";
 2320
 02321                if (encodingOptions.EncoderPreset < EncoderPreset.ultrafast)
 2322                {
 2323                    // The following params are slower than the ultrafast preset, don't use when ultrafast is selected.
 02324                    param += ":subme=3:merange=25:rc-lookahead=10:me=star:ctu=32:max-tu-size=32:min-cu-size=16:rskip=2:r
 2325                }
 2326            }
 2327
 02328            if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)
 02329                && _mediaEncoder.EncoderVersion >= _minFFmpegSvtAv1Params)
 2330            {
 02331                param += " -svtav1-params:0 rc=1:tune=0:film-grain=0:enable-overlays=1:enable-tf=0";
 2332            }
 2333
 2334            /* Access unit too large: 8192 < 20880 error */
 02335            if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) ||
 02336                 string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) &&
 02337                 _mediaEncoder.EncoderVersion >= _minFFmpegVaapiH26xEncA53CcSei)
 2338            {
 02339                param += " -sei -a53_cc";
 2340            }
 2341
 02342            return param;
 2343        }
 2344
 2345        public bool CanStreamCopyVideo(EncodingJobInfo state, MediaStream videoStream)
 2346        {
 02347            var request = state.BaseRequest;
 2348
 02349            if (!request.AllowVideoStreamCopy)
 2350            {
 02351                return false;
 2352            }
 2353
 02354            if (videoStream.IsInterlaced
 02355                && state.DeInterlace(videoStream.Codec, false))
 2356            {
 02357                return false;
 2358            }
 2359
 02360            if (videoStream.IsAnamorphic ?? false)
 2361            {
 02362                if (request.RequireNonAnamorphic)
 2363                {
 02364                    return false;
 2365                }
 2366            }
 2367
 2368            // Can't stream copy if we're burning in subtitles
 02369            if (request.SubtitleStreamIndex.HasValue
 02370                && request.SubtitleStreamIndex.Value >= 0
 02371                && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
 2372            {
 02373                return false;
 2374            }
 2375
 02376            if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 02377                && videoStream.IsAVC.HasValue
 02378                && !videoStream.IsAVC.Value
 02379                && request.RequireAvc)
 2380            {
 02381                return false;
 2382            }
 2383
 2384            // Source and target codecs must match
 02385            if (string.IsNullOrEmpty(videoStream.Codec)
 02386                || (state.SupportedVideoCodecs.Length != 0
 02387                    && !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparison.OrdinalIgnoreCase)))
 2388            {
 02389                return false;
 2390            }
 2391
 02392            var requestedProfiles = state.GetRequestedProfiles(videoStream.Codec);
 2393
 2394            // If client is requesting a specific video profile, it must match the source
 02395            if (requestedProfiles.Length > 0)
 2396            {
 02397                if (string.IsNullOrEmpty(videoStream.Profile))
 2398                {
 2399                    // return false;
 2400                }
 2401
 02402                var requestedProfile = requestedProfiles[0];
 2403                // strip spaces because they may be stripped out on the query string as well
 02404                if (!string.IsNullOrEmpty(videoStream.Profile)
 02405                    && !requestedProfiles.Contains(videoStream.Profile.Replace(" ", string.Empty, StringComparison.Ordin
 2406                {
 02407                    var currentScore = GetVideoProfileScore(videoStream.Codec, videoStream.Profile);
 02408                    var requestedScore = GetVideoProfileScore(videoStream.Codec, requestedProfile);
 2409
 02410                    if (currentScore == -1 || currentScore > requestedScore)
 2411                    {
 02412                        return false;
 2413                    }
 2414                }
 2415            }
 2416
 02417            var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
 02418            if (requestedRangeTypes.Length > 0)
 2419            {
 02420                if (videoStream.VideoRangeType == VideoRangeType.Unknown)
 2421                {
 02422                    return false;
 2423                }
 2424
 2425                // DOVIWithHDR10 should be compatible with HDR10 supporting players. Same goes with HLG and of course SD
 02426                var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ord
 02427                var requestHasHLG = requestedRangeTypes.Contains(VideoRangeType.HLG.ToString(), StringComparison.Ordinal
 02428                var requestHasSDR = requestedRangeTypes.Contains(VideoRangeType.SDR.ToString(), StringComparison.Ordinal
 02429                var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.Ordin
 2430
 2431                // If SDR is the only supported range, we should not copy any of the HDR streams.
 2432                // All the following copy check assumes at least one HDR format is supported.
 02433                if (requestedRangeTypes.Length == 1 && requestHasSDR && videoStream.VideoRangeType != VideoRangeType.SDR
 2434                {
 02435                    return false;
 2436                }
 2437
 2438                // If the client does not support DOVI and the video stream is DOVI without fallback, we should not copy
 02439                if (!requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVI)
 2440                {
 02441                    return false;
 2442                }
 2443
 02444                if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreC
 02445                     && !((requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10)
 02446                            || (requestHasHLG && videoStream.VideoRangeType == VideoRangeType.DOVIWithHLG)
 02447                            || (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)
 02448                            || (requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.HDR10Plus)))
 2449                {
 2450                    // If the video stream is in HDR10+ or a static HDR format, don't allow copy if the client does not 
 02451                    if (videoStream.VideoRangeType is VideoRangeType.HDR10Plus or VideoRangeType.HDR10 or VideoRangeType
 2452                    {
 02453                        return false;
 2454                    }
 2455
 2456                    // Check complicated cases where we need to remove dynamic metadata
 2457                    // Conservatively refuse to copy if the encoder can't remove dynamic metadata,
 2458                    // but a removal is required for compatability reasons.
 02459                    var dynamicHdrMetadataRemovalPlan = ShouldRemoveDynamicHdrMetadata(state);
 02460                    if (!CanEncoderRemoveDynamicHdrMetadata(dynamicHdrMetadataRemovalPlan, videoStream))
 2461                    {
 02462                        return false;
 2463                    }
 2464                }
 2465            }
 2466
 02467            var requestedRotations = state.GetRequestedRotations(videoStream.Codec);
 02468            if (requestedRotations.Length > 0)
 2469            {
 02470                var rotation = state.VideoStream?.Rotation ?? 0;
 02471                if (rotation != 0
 02472                    && !requestedRotations.Contains(rotation.ToString(CultureInfo.InvariantCulture), StringComparison.Or
 2473                {
 02474                    return false;
 2475                }
 2476            }
 2477
 2478            // Video width must fall within requested value
 02479            if (request.MaxWidth.HasValue
 02480                && (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
 2481            {
 02482                return false;
 2483            }
 2484
 2485            // Video height must fall within requested value
 02486            if (request.MaxHeight.HasValue
 02487                && (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value))
 2488            {
 02489                return false;
 2490            }
 2491
 2492            // Video framerate must fall within requested value
 02493            var requestedFramerate = request.MaxFramerate ?? request.Framerate;
 02494            if (requestedFramerate.HasValue)
 2495            {
 02496                var videoFrameRate = videoStream.ReferenceFrameRate;
 2497
 2498                // Add a little tolerance to the framerate check because some videos might record a framerate
 2499                // that is slightly greater than the intended framerate, but the device can still play it correctly.
 2500                // 0.05 fps tolerance should be safe enough.
 02501                if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value + 0.05f)
 2502                {
 02503                    return false;
 2504                }
 2505            }
 2506
 2507            // Video bitrate must fall within requested value
 02508            if (request.VideoBitRate.HasValue
 02509                && (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value))
 2510            {
 2511                // For LiveTV that has no bitrate, let's try copy if other conditions are met
 02512                if (string.IsNullOrWhiteSpace(request.LiveStreamId) || videoStream.BitRate.HasValue)
 2513                {
 02514                    return false;
 2515                }
 2516            }
 2517
 02518            var maxBitDepth = state.GetRequestedVideoBitDepth(videoStream.Codec);
 02519            if (maxBitDepth.HasValue)
 2520            {
 02521                if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > maxBitDepth.Value)
 2522                {
 02523                    return false;
 2524                }
 2525            }
 2526
 02527            var maxRefFrames = state.GetRequestedMaxRefFrames(videoStream.Codec);
 02528            if (maxRefFrames.HasValue
 02529                && videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > maxRefFrames.Value)
 2530            {
 02531                return false;
 2532            }
 2533
 2534            // If a specific level was requested, the source must match or be less than
 02535            var level = state.GetRequestedLevel(videoStream.Codec);
 02536            if (double.TryParse(level, CultureInfo.InvariantCulture, out var requestLevel))
 2537            {
 02538                if (!videoStream.Level.HasValue)
 2539                {
 2540                    // return false;
 2541                }
 2542
 02543                if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
 2544                {
 02545                    return false;
 2546                }
 2547            }
 2548
 02549            if (string.Equals(state.InputContainer, "avi", StringComparison.OrdinalIgnoreCase)
 02550                && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)
 02551                && !(videoStream.IsAVC ?? false))
 2552            {
 2553                // see Coach S01E01 - Kelly and the Professor(0).avi
 02554                return false;
 2555            }
 2556
 02557            return true;
 2558        }
 2559
 2560        public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, IEnumerable<string> supportedAudi
 2561        {
 02562            var request = state.BaseRequest;
 2563
 02564            if (!request.AllowAudioStreamCopy)
 2565            {
 02566                return false;
 2567            }
 2568
 02569            var maxBitDepth = state.GetRequestedAudioBitDepth(audioStream.Codec);
 02570            if (maxBitDepth.HasValue
 02571                && audioStream.BitDepth.HasValue
 02572                && audioStream.BitDepth.Value > maxBitDepth.Value)
 2573            {
 02574                return false;
 2575            }
 2576
 2577            // Source and target codecs must match
 02578            if (string.IsNullOrEmpty(audioStream.Codec)
 02579                || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparison.OrdinalIgnoreCase))
 2580            {
 02581                return false;
 2582            }
 2583
 2584            // Channels must fall within requested value
 02585            var channels = state.GetRequestedAudioChannels(audioStream.Codec);
 02586            if (channels.HasValue)
 2587            {
 02588                if (!audioStream.Channels.HasValue || audioStream.Channels.Value <= 0)
 2589                {
 02590                    return false;
 2591                }
 2592
 02593                if (audioStream.Channels.Value > channels.Value)
 2594                {
 02595                    return false;
 2596                }
 2597            }
 2598
 2599            // Sample rate must fall within requested value
 02600            if (request.AudioSampleRate.HasValue)
 2601            {
 02602                if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value <= 0)
 2603                {
 02604                    return false;
 2605                }
 2606
 02607                if (audioStream.SampleRate.Value > request.AudioSampleRate.Value)
 2608                {
 02609                    return false;
 2610                }
 2611            }
 2612
 2613            // Audio bitrate must fall within requested value
 02614            if (request.AudioBitRate.HasValue
 02615                && audioStream.BitRate.HasValue
 02616                && audioStream.BitRate.Value > request.AudioBitRate.Value)
 2617            {
 02618                return false;
 2619            }
 2620
 02621            return request.EnableAutoStreamCopy;
 2622        }
 2623
 2624        public int GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideo
 2625        {
 02626            var bitrate = request.VideoBitRate;
 2627
 02628            if (videoStream is not null)
 2629            {
 02630                var isUpscaling = request.Height.HasValue
 02631                    && videoStream.Height.HasValue
 02632                    && request.Height.Value > videoStream.Height.Value
 02633                    && request.Width.HasValue
 02634                    && videoStream.Width.HasValue
 02635                    && request.Width.Value > videoStream.Width.Value;
 2636
 2637                // Don't allow bitrate increases unless upscaling
 02638                if (!isUpscaling && bitrate.HasValue && videoStream.BitRate.HasValue)
 2639                {
 02640                    bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value);
 2641                }
 2642
 02643                if (bitrate.HasValue)
 2644                {
 02645                    var inputVideoCodec = videoStream.Codec;
 02646                    bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
 2647
 2648                    // If a max bitrate was requested, don't let the scaled bitrate exceed it
 02649                    if (request.VideoBitRate.HasValue)
 2650                    {
 02651                        bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
 2652                    }
 2653                }
 2654            }
 2655
 2656            // Cap the max target bitrate to 400 Mbps.
 2657            // No consumer or professional hardware transcode target exceeds this value
 2658            // (Intel QSV tops out at ~300 Mbps for H.264; HEVC High Tier Level 5.x is ~240 Mbps).
 2659            // Without this cap, plugin-provided MPEG-TS streams with no usable bitrate metadata
 2660            // can produce unreasonably large -bufsize/-maxrate values for the encoder.
 2661            // Note: the existing FallbackMaxStreamingBitrate mechanism (default 30 Mbps) only
 2662            // applies when a LiveStreamId is set (M3U/HDHR sources). Plugin streams and other
 2663            // sources that bypass the LiveTV pipeline are not covered by it.
 2664            const int MaxSaneBitrate = 400_000_000; // 400 Mbps
 02665            return Math.Min(bitrate ?? 0, MaxSaneBitrate);
 2666        }
 2667
 2668        private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
 2669        {
 2670            // these values were chosen from testing to improve low bitrate streams
 02671            if (sourceBitrate <= 2000000)
 2672            {
 02673                sourceBitrate = Convert.ToInt32(sourceBitrate * 2.5);
 2674            }
 02675            else if (sourceBitrate <= 3000000)
 2676            {
 02677                sourceBitrate *= 2;
 2678            }
 2679
 02680            var bitrate = Math.Min(sourceBitrate, requestedBitrate);
 2681
 02682            return bitrate;
 2683        }
 2684
 2685        private static double GetVideoBitrateScaleFactor(string codec)
 2686        {
 2687            // hevc & vp9 - 40% more efficient than h.264
 02688            if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 02689                || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
 02690                || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
 2691            {
 02692                return .6;
 2693            }
 2694
 2695            // av1 - 50% more efficient than h.264
 02696            if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 2697            {
 02698                return .5;
 2699            }
 2700
 02701            return 1;
 2702        }
 2703
 2704        public static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
 2705        {
 02706            var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
 02707            var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
 2708
 2709            // Don't scale the real bitrate lower than the requested bitrate
 02710            var scaleFactor = Math.Max(outputScaleFactor / inputScaleFactor, 1);
 2711
 02712            if (bitrate <= 500000)
 2713            {
 02714                scaleFactor = Math.Max(scaleFactor, 4);
 2715            }
 02716            else if (bitrate <= 1000000)
 2717            {
 02718                scaleFactor = Math.Max(scaleFactor, 3);
 2719            }
 02720            else if (bitrate <= 2000000)
 2721            {
 02722                scaleFactor = Math.Max(scaleFactor, 2.5);
 2723            }
 02724            else if (bitrate <= 3000000)
 2725            {
 02726                scaleFactor = Math.Max(scaleFactor, 2);
 2727            }
 02728            else if (bitrate >= 30000000)
 2729            {
 2730                // Don't scale beyond 30Mbps, it is hardly visually noticeable for most codecs with our prefer speed enc
 2731                // and will cause extremely high bitrate to be used for av1->h264 transcoding that will overload clients
 02732                scaleFactor = 1;
 2733            }
 2734
 02735            return Convert.ToInt32(scaleFactor * bitrate);
 2736        }
 2737
 2738        public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChanne
 2739        {
 02740            return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels);
 2741        }
 2742
 2743        public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudio
 2744        {
 02745            if (audioStream is null)
 2746            {
 02747                return null;
 2748            }
 2749
 02750            var inputChannels = audioStream.Channels ?? 0;
 02751            var outputChannels = outputAudioChannels ?? 0;
 02752            var bitrate = audioBitRate ?? int.MaxValue;
 2753
 02754            if (string.IsNullOrEmpty(audioCodec)
 02755                || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
 02756                || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
 02757                || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
 02758                || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
 02759                || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
 02760                || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
 2761            {
 2762#pragma warning disable SA1008
 02763                return (inputChannels, outputChannels) switch
 02764                {
 02765                    ( >= 6, >= 6 or 0) => Math.Min(640000, bitrate),
 02766                    ( > 0, > 0) => Math.Min(outputChannels * 128000, bitrate),
 02767                    ( > 0, _) => Math.Min(inputChannels * 128000, bitrate),
 02768                    (_, _) => Math.Min(384000, bitrate)
 02769                };
 2770#pragma warning restore SA1008
 2771            }
 2772
 02773            if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
 02774                || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
 2775            {
 2776#pragma warning disable SA1008
 02777                return (inputChannels, outputChannels) switch
 02778                {
 02779                    ( >= 6, >= 6 or 0) => Math.Min(768000, bitrate),
 02780                    ( > 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
 02781                    ( > 0, _) => Math.Min(inputChannels * 136000, bitrate),
 02782                    (_, _) => Math.Min(672000, bitrate)
 02783                };
 2784#pragma warning restore SA1008
 2785            }
 2786
 2787            // Empty bitrate area is not allow on iOS
 2788            // Default audio bitrate to 128K per channel if we don't have codec specific defaults
 2789            // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
 02790            return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
 2791        }
 2792
 2793        public string GetAudioVbrModeParam(string encoder, int bitrate, int channels)
 2794        {
 02795            var bitratePerChannel = bitrate / Math.Max(channels, 1);
 02796            if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
 2797            {
 02798                return " -vbr:a " + bitratePerChannel switch
 02799                {
 02800                    < 32000 => "1",
 02801                    < 48000 => "2",
 02802                    < 64000 => "3",
 02803                    < 96000 => "4",
 02804                    _ => "5"
 02805                };
 2806            }
 2807
 02808            if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase))
 2809            {
 2810                // lame's VBR is only good for a certain bitrate range
 2811                // For very low and very high bitrate, use abr mode
 02812                if (bitratePerChannel is < 122500 and > 48000)
 2813                {
 02814                    return " -qscale:a " + bitratePerChannel switch
 02815                    {
 02816                        < 64000 => "6",
 02817                        < 88000 => "4",
 02818                        < 112000 => "2",
 02819                        _ => "0"
 02820                    };
 2821                }
 2822
 02823                return " -abr:a 1" + " -b:a " + bitrate;
 2824            }
 2825
 02826            if (string.Equals(encoder, "aac_at", StringComparison.OrdinalIgnoreCase))
 2827            {
 2828                // aac_at's CVBR mode
 02829                return " -aac_at_mode:a 2" + " -b:a " + bitrate;
 2830            }
 2831
 02832            if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase))
 2833            {
 02834                return " -qscale:a " + bitratePerChannel switch
 02835                {
 02836                    < 40000 => "0",
 02837                    < 56000 => "2",
 02838                    < 80000 => "4",
 02839                    < 112000 => "6",
 02840                    _ => "8"
 02841                };
 2842            }
 2843
 02844            return null;
 2845        }
 2846
 2847        public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
 2848        {
 02849            var channels = state.OutputAudioChannels;
 2850
 02851            var filters = new List<string>();
 2852
 02853            if (channels is 2 && state.AudioStream?.Channels is > 2)
 2854            {
 02855                var hasDownMixFilter = DownMixAlgorithmsHelper.AlgorithmFilterStrings.TryGetValue((encodingOptions.DownM
 02856                if (hasDownMixFilter)
 2857                {
 02858                    filters.Add(downMixFilterString);
 2859                }
 2860
 02861                if (!encodingOptions.DownMixAudioBoost.Equals(1))
 2862                {
 02863                    filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
 2864                }
 2865            }
 2866
 02867            var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
 02868            if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && ShouldEncodeSubtitle(st
 2869            {
 02870                var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds;
 2871
 02872                filters.Add(
 02873                    string.Format(
 02874                        CultureInfo.InvariantCulture,
 02875                        "asetpts=PTS-{0}/TB",
 02876                        Math.Round(seconds)));
 2877            }
 2878
 02879            if (filters.Count > 0)
 2880            {
 02881                return " -af \"" + string.Join(',', filters) + "\"";
 2882            }
 2883
 02884            return string.Empty;
 2885        }
 2886
 2887        /// <summary>
 2888        /// Gets the number of audio channels to specify on the command line.
 2889        /// </summary>
 2890        /// <param name="state">The state.</param>
 2891        /// <param name="audioStream">The audio stream.</param>
 2892        /// <param name="outputAudioCodec">The output audio codec.</param>
 2893        /// <returns>System.Nullable{System.Int32}.</returns>
 2894        public int? GetNumAudioChannelsParam(EncodingJobInfo state, MediaStream audioStream, string outputAudioCodec)
 2895        {
 02896            if (audioStream is null)
 2897            {
 02898                return null;
 2899            }
 2900
 02901            var request = state.BaseRequest;
 2902
 02903            var codec = outputAudioCodec ?? string.Empty;
 2904
 02905            int? resultChannels = state.GetRequestedAudioChannels(codec);
 2906
 02907            var inputChannels = audioStream.Channels;
 2908
 02909            if (inputChannels > 0)
 2910            {
 02911                resultChannels = inputChannels < resultChannels ? inputChannels : resultChannels ?? inputChannels;
 2912            }
 2913
 02914            var isTranscodingAudio = !IsCopyCodec(codec);
 2915
 02916            if (isTranscodingAudio)
 2917            {
 02918                var audioEncoder = GetAudioEncoder(state);
 02919                if (!_audioTranscodeChannelLookup.TryGetValue(audioEncoder, out var transcoderChannelLimit))
 2920                {
 2921                    // Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many c
 02922                    transcoderChannelLimit = 8;
 2923                }
 2924
 2925                // Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelL
 02926                resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? tr
 2927
 02928                if (request.TranscodingMaxAudioChannels < resultChannels)
 2929                {
 02930                    resultChannels = request.TranscodingMaxAudioChannels;
 2931                }
 2932
 2933                // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
 2934                // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_d
 02935                if (state.TranscodingType != TranscodingJobType.Progressive
 02936                    && ((resultChannels > 2 && resultChannels < 6) || resultChannels == 7))
 2937                {
 2938                    // We can let FFMpeg supply an extra LFE channel for 5ch and 7ch to make them 5.1 and 7.1
 02939                    if (resultChannels == 5)
 2940                    {
 02941                        resultChannels = 6;
 2942                    }
 02943                    else if (resultChannels == 7)
 2944                    {
 02945                        resultChannels = 8;
 2946                    }
 2947                    else
 2948                    {
 2949                        // For other weird layout, just downmix to stereo for compatibility
 02950                        resultChannels = 2;
 2951                    }
 2952                }
 2953            }
 2954
 02955            return resultChannels;
 2956        }
 2957
 2958        /// <summary>
 2959        /// Enforces the resolution limit.
 2960        /// </summary>
 2961        /// <param name="state">The state.</param>
 2962        public void EnforceResolutionLimit(EncodingJobInfo state)
 2963        {
 02964            var videoRequest = state.BaseRequest;
 2965
 2966            // Switch the incoming params to be ceilings rather than fixed values
 02967            videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
 02968            videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
 2969
 02970            videoRequest.Width = null;
 02971            videoRequest.Height = null;
 02972        }
 2973
 2974        /// <summary>
 2975        /// Gets the fast seek command line parameter.
 2976        /// </summary>
 2977        /// <param name="state">The state.</param>
 2978        /// <param name="options">The options.</param>
 2979        /// <param name="segmentContainer">Segment Container.</param>
 2980        /// <returns>System.String.</returns>
 2981        /// <value>The fast seek command line parameter.</value>
 2982        public string GetFastSeekCommandLineParameter(EncodingJobInfo state, EncodingOptions options, string segmentCont
 2983        {
 42984            var time = state.BaseRequest.StartTimeTicks ?? 0;
 42985            var maxTime = state.RunTimeTicks ?? 0;
 42986            var seekParam = string.Empty;
 2987
 42988            if (time > 0)
 2989            {
 2990                // For direct streaming/remuxing, HLS segments start at keyframes.
 2991                // However, ffmpeg will seek to previous keyframe when the exact frame time is the input
 2992                // Workaround this by adding 0.5s offset to the seeking time to get the exact keyframe on most videos.
 2993                // This will help subtitle syncing.
 02994                var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCod
 02995                var seekTick = isHlsRemuxing ? time + 5000000L : time;
 2996
 2997                // Seeking beyond EOF makes no sense in transcoding. Clamp the seekTick value to
 2998                // [0, RuntimeTicks - 5.0s], so that the muxer gets packets and avoid error codes.
 02999                if (maxTime > 0)
 3000                {
 03001                    seekTick = Math.Clamp(seekTick, 0, Math.Max(maxTime - 50000000L, 0));
 3002                }
 3003
 03004                seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(seekT
 3005
 03006                if (state.IsVideoRequest)
 3007                {
 3008                    // If we are remuxing, then the copied stream cannot be seeked accurately (it will seek to the neare
 3009                    // keyframe). If we are using fMP4, then force all other streams to use the same inaccurate seeking 
 3010                    // avoid A/V sync issues which cause playback issues on some devices.
 3011                    // When remuxing video, the segment start times correspond to key frames in the source stream, so th
 3012                    // option shouldn't change the seeked point that much.
 3013                    // Important: make sure not to use it with wtv because it breaks seeking
 03014                    if (state.TranscodingType is TranscodingJobType.Hls
 03015                        && string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase)
 03016                        && (IsCopyCodec(state.OutputVideoCodec) || IsCopyCodec(state.OutputAudioCodec))
 03017                        && !string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase))
 3018                    {
 03019                        seekParam += " -noaccurate_seek";
 3020                    }
 3021                }
 3022            }
 3023
 43024            return seekParam;
 3025        }
 3026
 3027        /// <summary>
 3028        /// Gets the map args.
 3029        /// </summary>
 3030        /// <param name="state">The state.</param>
 3031        /// <returns>System.String.</returns>
 3032        public string GetMapArgs(EncodingJobInfo state)
 3033        {
 3034            // If we don't have known media info
 3035            // If input is video, use -sn to drop subtitles
 3036            // Otherwise just return empty
 73037            if (state.VideoStream is null && state.AudioStream is null)
 3038            {
 03039                return state.IsInputVideo ? "-sn" : string.Empty;
 3040            }
 3041
 3042            // We have media info, but we don't know the stream index
 73043            if (state.VideoStream is not null && state.VideoStream.Index == -1)
 3044            {
 03045                return "-sn";
 3046            }
 3047
 3048            // We have media info, but we don't know the stream index
 73049            if (state.AudioStream is not null && state.AudioStream.Index == -1)
 3050            {
 03051                return state.IsInputVideo ? "-sn" : string.Empty;
 3052            }
 3053
 73054            var args = string.Empty;
 3055
 73056            if (state.VideoStream is not null)
 3057            {
 73058                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 3059
 73060                args += string.Format(
 73061                    CultureInfo.InvariantCulture,
 73062                    "-map 0:{0}",
 73063                    videoStreamIndex);
 3064            }
 3065            else
 3066            {
 3067                // No known video stream
 03068                args += "-vn";
 3069            }
 3070
 73071            if (state.AudioStream is not null)
 3072            {
 73073                int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
 73074                if (state.AudioStream.IsExternal)
 3075                {
 03076                    bool hasExternalSubAsInput = NeedsExternalSubtitleMuxing(state);
 03077                    int externalAudioMapIndex = hasExternalSubAsInput ? 2 : 1;
 3078
 03079                    args += string.Format(
 03080                        CultureInfo.InvariantCulture,
 03081                        " -map {0}:{1}",
 03082                        externalAudioMapIndex,
 03083                        audioStreamIndex);
 3084                }
 3085                else
 3086                {
 73087                    args += string.Format(
 73088                        CultureInfo.InvariantCulture,
 73089                        " -map 0:{0}",
 73090                        audioStreamIndex);
 3091                }
 3092            }
 3093            else
 3094            {
 03095                args += " -map -0:a";
 3096            }
 3097
 73098            var subtitleMethod = state.SubtitleDeliveryMethod;
 73099            if (state.SubtitleStream is null || subtitleMethod == SubtitleDeliveryMethod.Hls)
 3100            {
 13101                args += " -map -0:s";
 3102            }
 63103            else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
 3104            {
 63105                if (state.SubtitleStream.IsExternal)
 3106                {
 3107                    // External subtitle file is added as second FFmpeg input.
 3108                    // For single-stream files (SRT/ASS/VTT) the in-file index is always 0.
 3109                    // For multi-stream containers (MKS) we count how many streams from
 3110                    // the same file appear before the selected one.
 43111                    var inFileIndex = state.MediaSource.MediaStreams
 43112                        .Where(s => string.Equals(s.Path, state.SubtitleStream.Path, StringComparison.Ordinal))
 43113                        .TakeWhile(s => s.Index != state.SubtitleStream.Index)
 43114                        .Count();
 3115
 43116                    args += string.Format(
 43117                        CultureInfo.InvariantCulture,
 43118                        " -map 1:{0}",
 43119                        inFileIndex);
 3120                }
 3121                else
 3122                {
 23123                    int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3124
 23125                    args += string.Format(
 23126                        CultureInfo.InvariantCulture,
 23127                        " -map 0:{0}",
 23128                        subtitleStreamIndex);
 3129                }
 3130            }
 03131            else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
 3132            {
 03133                int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3134
 03135                args += string.Format(
 03136                    CultureInfo.InvariantCulture,
 03137                    " -map 1:{0} -sn",
 03138                    externalSubtitleStreamIndex);
 3139            }
 3140
 73141            return args;
 3142        }
 3143
 3144        /// <summary>
 3145        /// Gets the negative map args by filters.
 3146        /// </summary>
 3147        /// <param name="state">The state.</param>
 3148        /// <param name="videoProcessFilters">The videoProcessFilters.</param>
 3149        /// <returns>System.String.</returns>
 3150        public string GetNegativeMapArgsByFilters(EncodingJobInfo state, string videoProcessFilters)
 3151        {
 03152            string args = string.Empty;
 3153
 3154            // http://ffmpeg.org/ffmpeg-all.html#toc-Complex-filtergraphs-1
 03155            if (state.VideoStream is not null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordina
 3156            {
 03157                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 3158
 03159                args += string.Format(
 03160                    CultureInfo.InvariantCulture,
 03161                    "-map -0:{0} ",
 03162                    videoStreamIndex);
 3163            }
 3164
 03165            return args;
 3166        }
 3167
 3168        /// <summary>
 3169        /// Determines which stream will be used for playback.
 3170        /// </summary>
 3171        /// <param name="allStream">All stream.</param>
 3172        /// <param name="desiredIndex">Index of the desired.</param>
 3173        /// <param name="type">The type.</param>
 3174        /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
 3175        /// <returns>MediaStream.</returns>
 3176        public MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, b
 3177        {
 03178            var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
 3179
 03180            if (desiredIndex.HasValue)
 3181            {
 03182                var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
 3183
 03184                if (stream is not null)
 3185                {
 03186                    return stream;
 3187                }
 3188            }
 3189
 03190            if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
 3191            {
 03192                return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
 03193                       streams.FirstOrDefault();
 3194            }
 3195
 3196            // Just return the first one
 03197            return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
 3198        }
 3199
 3200        public static (int? Width, int? Height) GetFixedOutputSize(
 3201            int? videoWidth,
 3202            int? videoHeight,
 3203            int? requestedWidth,
 3204            int? requestedHeight,
 3205            int? requestedMaxWidth,
 3206            int? requestedMaxHeight)
 3207        {
 03208            if (!videoWidth.HasValue && !requestedWidth.HasValue)
 3209            {
 03210                return (null, null);
 3211            }
 3212
 03213            if (!videoHeight.HasValue && !requestedHeight.HasValue)
 3214            {
 03215                return (null, null);
 3216            }
 3217
 03218            int inputWidth = Convert.ToInt32(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture);
 03219            int inputHeight = Convert.ToInt32(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture);
 03220            int outputWidth = requestedWidth ?? inputWidth;
 03221            int outputHeight = requestedHeight ?? inputHeight;
 3222
 3223            // Don't transcode video to bigger than 4k when using HW.
 03224            int maximumWidth = Math.Min(requestedMaxWidth ?? outputWidth, 4096);
 03225            int maximumHeight = Math.Min(requestedMaxHeight ?? outputHeight, 4096);
 3226
 03227            if (outputWidth > maximumWidth || outputHeight > maximumHeight)
 3228            {
 03229                var scaleW = (double)maximumWidth / outputWidth;
 03230                var scaleH = (double)maximumHeight / outputHeight;
 03231                var scale = Math.Min(scaleW, scaleH);
 03232                outputWidth = Math.Min(maximumWidth, Convert.ToInt32(outputWidth * scale));
 03233                outputHeight = Math.Min(maximumHeight, Convert.ToInt32(outputHeight * scale));
 3234            }
 3235
 03236            outputWidth = 2 * (outputWidth / 2);
 03237            outputHeight = 2 * (outputHeight / 2);
 3238
 03239            return (outputWidth, outputHeight);
 3240        }
 3241
 3242        public static bool IsScaleRatioSupported(
 3243            int? videoWidth,
 3244            int? videoHeight,
 3245            int? requestedWidth,
 3246            int? requestedHeight,
 3247            int? requestedMaxWidth,
 3248            int? requestedMaxHeight,
 3249            double? maxScaleRatio)
 3250        {
 03251            var (outWidth, outHeight) = GetFixedOutputSize(
 03252                videoWidth,
 03253                videoHeight,
 03254                requestedWidth,
 03255                requestedHeight,
 03256                requestedMaxWidth,
 03257                requestedMaxHeight);
 3258
 03259            if (!videoWidth.HasValue
 03260                 || !videoHeight.HasValue
 03261                 || !outWidth.HasValue
 03262                 || !outHeight.HasValue
 03263                 || !maxScaleRatio.HasValue
 03264                 || (maxScaleRatio.Value < 1.0f))
 3265            {
 03266                return false;
 3267            }
 3268
 03269            var minScaleRatio = 1.0f / maxScaleRatio;
 03270            var scaleRatioW = (double)outWidth / (double)videoWidth;
 03271            var scaleRatioH = (double)outHeight / (double)videoHeight;
 3272
 03273            if (scaleRatioW < minScaleRatio
 03274                || scaleRatioW > maxScaleRatio
 03275                || scaleRatioH < minScaleRatio
 03276                || scaleRatioH > maxScaleRatio)
 3277            {
 03278                return false;
 3279            }
 3280
 03281            return true;
 3282        }
 3283
 3284        public static string GetHwScaleFilter(
 3285            string hwScalePrefix,
 3286            string hwScaleSuffix,
 3287            string videoFormat,
 3288            bool swapOutputWandH,
 3289            int? videoWidth,
 3290            int? videoHeight,
 3291            int? requestedWidth,
 3292            int? requestedHeight,
 3293            int? requestedMaxWidth,
 3294            int? requestedMaxHeight)
 3295        {
 03296            var (outWidth, outHeight) = GetFixedOutputSize(
 03297                videoWidth,
 03298                videoHeight,
 03299                requestedWidth,
 03300                requestedHeight,
 03301                requestedMaxWidth,
 03302                requestedMaxHeight);
 3303
 03304            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03305            var isSizeFixed = !videoWidth.HasValue
 03306                || outWidth.Value != videoWidth.Value
 03307                || !videoHeight.HasValue
 03308                || outHeight.Value != videoHeight.Value;
 3309
 03310            var swpOutW = swapOutputWandH ? outHeight.Value : outWidth.Value;
 03311            var swpOutH = swapOutputWandH ? outWidth.Value : outHeight.Value;
 3312
 03313            var arg1 = isSizeFixed ? $"=w={swpOutW}:h={swpOutH}" : string.Empty;
 03314            var arg2 = isFormatFixed ? $"format={videoFormat}" : string.Empty;
 03315            if (isFormatFixed)
 3316            {
 03317                arg2 = (isSizeFixed ? ':' : '=') + arg2;
 3318            }
 3319
 03320            if (!string.IsNullOrEmpty(hwScaleSuffix) && (isSizeFixed || isFormatFixed))
 3321            {
 03322                return string.Format(
 03323                    CultureInfo.InvariantCulture,
 03324                    "{0}_{1}{2}{3}",
 03325                    hwScalePrefix ?? "scale",
 03326                    hwScaleSuffix,
 03327                    arg1,
 03328                    arg2);
 3329            }
 3330
 03331            return string.Empty;
 3332        }
 3333
 3334        public static string GetGraphicalSubPreProcessFilters(
 3335            int? videoWidth,
 3336            int? videoHeight,
 3337            int? subtitleWidth,
 3338            int? subtitleHeight,
 3339            int? requestedWidth,
 3340            int? requestedHeight,
 3341            int? requestedMaxWidth,
 3342            int? requestedMaxHeight)
 3343        {
 03344            var (outWidth, outHeight) = GetFixedOutputSize(
 03345                videoWidth,
 03346                videoHeight,
 03347                requestedWidth,
 03348                requestedHeight,
 03349                requestedMaxWidth,
 03350                requestedMaxHeight);
 3351
 03352            if (!outWidth.HasValue
 03353                || !outHeight.HasValue
 03354                || outWidth.Value <= 0
 03355                || outHeight.Value <= 0)
 3356            {
 03357                return string.Empty;
 3358            }
 3359
 3360            // Automatically add padding based on subtitle input
 03361            var filters = @"scale,scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:blac
 3362
 03363            if (subtitleWidth.HasValue
 03364                && subtitleHeight.HasValue
 03365                && subtitleWidth.Value > 0
 03366                && subtitleHeight.Value > 0)
 3367            {
 03368                var videoDar = (double)outWidth.Value / outHeight.Value;
 03369                var subtitleDar = (double)subtitleWidth.Value / subtitleHeight.Value;
 3370
 3371                // No need to add padding when DAR is the same -> 1080p PGSSUB on 2160p video
 03372                if (Math.Abs(videoDar - subtitleDar) < 0.01f)
 3373                {
 03374                    filters = @"scale,scale={0}:{1}:fast_bilinear";
 3375                }
 3376            }
 3377
 03378            return string.Format(
 03379                CultureInfo.InvariantCulture,
 03380                filters,
 03381                outWidth.Value,
 03382                outHeight.Value);
 3383        }
 3384
 3385        public static string GetAlphaSrcFilter(
 3386            EncodingJobInfo state,
 3387            int? videoWidth,
 3388            int? videoHeight,
 3389            int? requestedWidth,
 3390            int? requestedHeight,
 3391            int? requestedMaxWidth,
 3392            int? requestedMaxHeight,
 3393            float? framerate)
 3394        {
 03395            var reqTicks = state.BaseRequest.StartTimeTicks ?? 0;
 03396            var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture
 03397            var (outWidth, outHeight) = GetFixedOutputSize(
 03398                videoWidth,
 03399                videoHeight,
 03400                requestedWidth,
 03401                requestedHeight,
 03402                requestedMaxWidth,
 03403                requestedMaxHeight);
 3404
 03405            if (outWidth.HasValue && outHeight.HasValue)
 3406            {
 03407                return string.Format(
 03408                    CultureInfo.InvariantCulture,
 03409                    "alphasrc=s={0}x{1}:r={2}:start='{3}'",
 03410                    outWidth.Value,
 03411                    outHeight.Value,
 03412                    framerate ?? 25,
 03413                    reqTicks > 0 ? startTime : 0);
 3414            }
 3415
 03416            return string.Empty;
 3417        }
 3418
 3419        public static string GetSwScaleFilter(
 3420            EncodingJobInfo state,
 3421            EncodingOptions options,
 3422            string videoEncoder,
 3423            int? videoWidth,
 3424            int? videoHeight,
 3425            Video3DFormat? threedFormat,
 3426            int? requestedWidth,
 3427            int? requestedHeight,
 3428            int? requestedMaxWidth,
 3429            int? requestedMaxHeight)
 3430        {
 03431            var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 03432            var isMjpeg = videoEncoder is not null && videoEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase)
 03433            var scaleVal = isV4l2 ? 64 : 2;
 03434            var targetAr = isMjpeg ? "(a*sar)" : "a"; // manually calculate AR when using mjpeg encoder
 3435
 3436            // If fixed dimensions were supplied
 03437            if (requestedWidth.HasValue && requestedHeight.HasValue)
 3438            {
 03439                if (isV4l2)
 3440                {
 03441                    var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 03442                    var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3443
 03444                    return string.Format(
 03445                            CultureInfo.InvariantCulture,
 03446                            "scale=trunc({0}/64)*64:trunc({1}/2)*2",
 03447                            widthParam,
 03448                            heightParam);
 3449                }
 3450
 03451                return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
 3452            }
 3453
 3454            // If Max dimensions were supplied, for width selects lowest even number between input width and width req s
 3455
 03456            if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
 3457            {
 03458                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 03459                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3460
 03461                return string.Format(
 03462                    CultureInfo.InvariantCulture,
 03463                    @"scale=trunc(min(max(iw\,ih*{3})\,min({0}\,{1}*{3}))/{2})*{2}:trunc(min(max(iw/{3}\,ih)\,min({0}/{3
 03464                    maxWidthParam,
 03465                    maxHeightParam,
 03466                    scaleVal,
 03467                    targetAr);
 3468            }
 3469
 3470            // If a fixed width was requested
 03471            if (requestedWidth.HasValue)
 3472            {
 03473                if (threedFormat.HasValue)
 3474                {
 3475                    // This method can handle 0 being passed in for the requested height
 03476                    return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0);
 3477                }
 3478
 03479                var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 3480
 03481                return string.Format(
 03482                    CultureInfo.InvariantCulture,
 03483                    "scale={0}:trunc(ow/{1}/2)*2",
 03484                    widthParam,
 03485                    targetAr);
 3486            }
 3487
 3488            // If a fixed height was requested
 03489            if (requestedHeight.HasValue)
 3490            {
 03491                var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3492
 03493                return string.Format(
 03494                    CultureInfo.InvariantCulture,
 03495                    "scale=trunc(oh*{2}/{1})*{1}:{0}",
 03496                    heightParam,
 03497                    scaleVal,
 03498                    targetAr);
 3499            }
 3500
 3501            // If a max width was requested
 03502            if (requestedMaxWidth.HasValue)
 3503            {
 03504                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 3505
 03506                return string.Format(
 03507                    CultureInfo.InvariantCulture,
 03508                    @"scale=trunc(min(max(iw\,ih*{2})\,{0})/{1})*{1}:trunc(ow/{2}/2)*2",
 03509                    maxWidthParam,
 03510                    scaleVal,
 03511                    targetAr);
 3512            }
 3513
 3514            // If a max height was requested
 03515            if (requestedMaxHeight.HasValue)
 3516            {
 03517                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3518
 03519                return string.Format(
 03520                    CultureInfo.InvariantCulture,
 03521                    @"scale=trunc(oh*{2}/{1})*{1}:min(max(iw/{2}\,ih)\,{0})",
 03522                    maxHeightParam,
 03523                    scaleVal,
 03524                    targetAr);
 3525            }
 3526
 03527            return string.Empty;
 3528        }
 3529
 3530        private static string GetFixedSwScaleFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight
 3531        {
 03532            var widthParam = requestedWidth.ToString(CultureInfo.InvariantCulture);
 03533            var heightParam = requestedHeight.ToString(CultureInfo.InvariantCulture);
 3534
 03535            string filter = null;
 3536
 03537            if (threedFormat.HasValue)
 3538            {
 03539                switch (threedFormat.Value)
 3540                {
 3541                    case Video3DFormat.HalfSideBySide:
 03542                        filter = @"crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(i
 3543                        // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars
 03544                        break;
 3545                    case Video3DFormat.FullSideBySide:
 03546                        filter = @"crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3547                        // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the s
 03548                        break;
 3549                    case Video3DFormat.HalfTopAndBottom:
 03550                        filter = @"crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(
 3551                        // htab crop height in half,scale to correct size, set the display aspect,crop out any black bar
 03552                        break;
 3553                    case Video3DFormat.FullTopAndBottom:
 03554                        filter = @"crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3555                        // ftab crop height in half, set the display aspect,crop out any black bars we may have made the
 3556                        break;
 3557                    default:
 3558                        break;
 3559                }
 3560            }
 3561
 3562            // default
 03563            if (filter is null)
 3564            {
 03565                if (requestedHeight > 0)
 3566                {
 03567                    filter = "scale=trunc({0}/2)*2:trunc({1}/2)*2";
 3568                }
 3569                else
 3570                {
 03571                    filter = "scale={0}:trunc({0}/a/2)*2";
 3572                }
 3573            }
 3574
 03575            return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam);
 3576        }
 3577
 3578        public static string GetSwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options)
 3579        {
 03580            var doubleRateDeint = options.DeinterlaceDoubleRate && state.VideoStream?.ReferenceFrameRate <= 30;
 03581            return string.Format(
 03582                CultureInfo.InvariantCulture,
 03583                "{0}={1}:-1:0",
 03584                options.DeinterlaceMethod.ToString().ToLowerInvariant(),
 03585                doubleRateDeint ? "1" : "0");
 3586        }
 3587
 3588        public string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix)
 3589        {
 03590            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03591            if (hwDeintSuffix.Contains("cuda", StringComparison.OrdinalIgnoreCase))
 3592            {
 03593                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3594
 03595                return string.Format(
 03596                    CultureInfo.InvariantCulture,
 03597                    "{0}_cuda={1}:-1:0",
 03598                    useBwdif ? "bwdif" : "yadif",
 03599                    doubleRateDeint ? "1" : "0");
 3600            }
 3601
 03602            if (hwDeintSuffix.Contains("opencl", StringComparison.OrdinalIgnoreCase))
 3603            {
 03604                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif;
 3605
 03606                if (_mediaEncoder.SupportsFilter("yadif_opencl")
 03607                    && _mediaEncoder.SupportsFilter("bwdif_opencl"))
 3608                {
 03609                    return string.Format(
 03610                        CultureInfo.InvariantCulture,
 03611                        "{0}_opencl={1}:-1:0",
 03612                        useBwdif ? "bwdif" : "yadif",
 03613                        doubleRateDeint ? "1" : "0");
 3614                }
 3615            }
 3616
 03617            if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
 3618            {
 03619                return string.Format(
 03620                    CultureInfo.InvariantCulture,
 03621                    "deinterlace_vaapi=rate={0}",
 03622                    doubleRateDeint ? "field" : "frame");
 3623            }
 3624
 03625            if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
 3626            {
 03627                return "deinterlace_qsv=mode=2";
 3628            }
 3629
 03630            if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
 3631            {
 03632                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3633
 03634                return string.Format(
 03635                    CultureInfo.InvariantCulture,
 03636                    "{0}_videotoolbox={1}:-1:0",
 03637                    useBwdif ? "bwdif" : "yadif",
 03638                    doubleRateDeint ? "1" : "0");
 3639            }
 3640
 03641            return string.Empty;
 3642        }
 3643
 3644        private string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat, bool forc
 3645        {
 03646            if (string.IsNullOrEmpty(hwTonemapSuffix))
 3647            {
 03648                return string.Empty;
 3649            }
 3650
 03651            var args = string.Empty;
 03652            var algorithm = options.TonemappingAlgorithm.ToString().ToLowerInvariant();
 03653            var mode = options.TonemappingMode.ToString().ToLowerInvariant();
 03654            var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 03655            var rangeString = range.ToString().ToLowerInvariant();
 3656
 03657            if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
 3658            {
 03659                var doVaVppProcamp = false;
 03660                var procampParams = string.Empty;
 03661                if (options.VppTonemappingBrightness != 0
 03662                    && options.VppTonemappingBrightness >= -100
 03663                    && options.VppTonemappingBrightness <= 100)
 3664                {
 03665                    procampParams += "procamp_vaapi=b={0}";
 03666                    doVaVppProcamp = true;
 3667                }
 3668
 03669                if (options.VppTonemappingContrast > 1
 03670                    && options.VppTonemappingContrast <= 10)
 3671                {
 03672                    procampParams += doVaVppProcamp ? ":c={1}" : "procamp_vaapi=c={1}";
 03673                    doVaVppProcamp = true;
 3674                }
 3675
 03676                args = procampParams + "{2}tonemap_vaapi=format={3}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
 3677
 03678                return string.Format(
 03679                        CultureInfo.InvariantCulture,
 03680                        args,
 03681                        options.VppTonemappingBrightness,
 03682                        options.VppTonemappingContrast,
 03683                        doVaVppProcamp ? "," : string.Empty,
 03684                        videoFormat ?? "nv12");
 3685            }
 3686            else
 3687            {
 03688                args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
 3689
 03690                var useLegacyTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode
 03691                                           && _legacyTonemapModes.Contains(options.TonemappingMode);
 3692
 03693                var useAdvancedTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegAdvancedTonemapMode
 03694                                              && _advancedTonemapModes.Contains(options.TonemappingMode);
 3695
 03696                if (useLegacyTonemapModes || useAdvancedTonemapModes)
 3697                {
 03698                    args += ":tonemap_mode={5}";
 3699                }
 3700
 03701                if (options.TonemappingParam != 0)
 3702                {
 03703                    args += ":param={6}";
 3704                }
 3705
 03706                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3707                {
 03708                    args += ":range={7}";
 3709                }
 3710            }
 3711
 03712            return string.Format(
 03713                    CultureInfo.InvariantCulture,
 03714                    args,
 03715                    hwTonemapSuffix,
 03716                    videoFormat ?? "nv12",
 03717                    algorithm,
 03718                    options.TonemappingPeak,
 03719                    options.TonemappingDesat,
 03720                    mode,
 03721                    options.TonemappingParam,
 03722                    rangeString);
 3723        }
 3724
 3725        private string GetLibplaceboFilter(
 3726            EncodingOptions options,
 3727            string videoFormat,
 3728            bool doTonemap,
 3729            int? videoWidth,
 3730            int? videoHeight,
 3731            int? requestedWidth,
 3732            int? requestedHeight,
 3733            int? requestedMaxWidth,
 3734            int? requestedMaxHeight,
 3735            bool forceFullRange)
 3736        {
 03737            var (outWidth, outHeight) = GetFixedOutputSize(
 03738                videoWidth,
 03739                videoHeight,
 03740                requestedWidth,
 03741                requestedHeight,
 03742                requestedMaxWidth,
 03743                requestedMaxHeight);
 3744
 03745            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03746            var isSizeFixed = !videoWidth.HasValue
 03747                || outWidth.Value != videoWidth.Value
 03748                || !videoHeight.HasValue
 03749                || outHeight.Value != videoHeight.Value;
 3750
 03751            var sizeArg = isSizeFixed ? (":w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty;
 03752            var formatArg = isFormatFixed ? (":format=" + videoFormat) : string.Empty;
 03753            var tonemapArg = string.Empty;
 3754
 03755            if (doTonemap)
 3756            {
 03757                var algorithm = options.TonemappingAlgorithm;
 03758                var algorithmString = "clip";
 03759                var mode = options.TonemappingMode;
 03760                var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 3761
 03762                if (algorithm == TonemappingAlgorithm.bt2390)
 3763                {
 03764                    algorithmString = "bt.2390";
 3765                }
 03766                else if (algorithm != TonemappingAlgorithm.none)
 3767                {
 03768                    algorithmString = algorithm.ToString().ToLowerInvariant();
 3769                }
 3770
 03771                tonemapArg = $":tonemapping={algorithmString}:peak_detect=0:color_primaries=bt709:color_trc=bt709:colors
 3772
 03773                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3774                {
 03775                    tonemapArg += ":range=" + range.ToString().ToLowerInvariant();
 3776                }
 3777            }
 3778
 03779            return string.Format(
 03780                CultureInfo.InvariantCulture,
 03781                "libplacebo=upscaler=none:downscaler=none{0}{1}{2}",
 03782                sizeArg,
 03783                formatArg,
 03784                tonemapArg);
 3785        }
 3786
 3787        public string GetVideoTransposeDirection(EncodingJobInfo state)
 3788        {
 03789            return (state.VideoStream?.Rotation ?? 0) switch
 03790            {
 03791                90 => "cclock",
 03792                180 => "reversal",
 03793                -90 => "clock",
 03794                -180 => "reversal",
 03795                _ => string.Empty
 03796            };
 3797        }
 3798
 3799        /// <summary>
 3800        /// Gets the parameter of software filter chain.
 3801        /// </summary>
 3802        /// <param name="state">Encoding state.</param>
 3803        /// <param name="options">Encoding options.</param>
 3804        /// <param name="vidEncoder">Video encoder to use.</param>
 3805        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3806        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetSwVidFilterChain(
 3807            EncodingJobInfo state,
 3808            EncodingOptions options,
 3809            string vidEncoder)
 3810        {
 03811            var inW = state.VideoStream?.Width;
 03812            var inH = state.VideoStream?.Height;
 03813            var reqW = state.BaseRequest.Width;
 03814            var reqH = state.BaseRequest.Height;
 03815            var reqMaxW = state.BaseRequest.MaxWidth;
 03816            var reqMaxH = state.BaseRequest.MaxHeight;
 03817            var threeDFormat = state.MediaSource.Video3DFormat;
 3818
 03819            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03820            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03821            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 03822            var isV4l2Encoder = vidEncoder.Contains("h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 3823
 03824            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03825            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03826            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03827            var doToneMap = IsSwTonemapAvailable(state, options);
 03828            var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI;
 3829
 03830            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03831            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03832            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 3833
 03834            var rotation = state.VideoStream?.Rotation ?? 0;
 03835            var swapWAndH = Math.Abs(rotation) == 90;
 03836            var swpInW = swapWAndH ? inH : inW;
 03837            var swpInH = swapWAndH ? inW : inH;
 3838
 3839            /* Make main filters for video stream */
 03840            var mainFilters = new List<string>();
 3841
 03842            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doToneMap));
 3843
 3844            // INPUT sw surface(memory/copy-back from vram)
 3845            // sw deint
 03846            if (doDeintH2645)
 3847            {
 03848                var deintFilter = GetSwDeinterlaceFilter(state, options);
 03849                mainFilters.Add(deintFilter);
 3850            }
 3851
 03852            var outFormat = isSwDecoder ? "yuv420p" : "nv12";
 03853            var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, r
 03854            if (isVaapiEncoder)
 3855            {
 03856                outFormat = "nv12";
 3857            }
 03858            else if (isV4l2Encoder)
 3859            {
 03860                outFormat = "yuv420p";
 3861            }
 3862
 3863            // sw scale
 03864            mainFilters.Add(swScaleFilter);
 3865
 3866            // sw tonemap
 03867            if (doToneMap)
 3868            {
 3869                // tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary
 03870                var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat;
 03871                var tonemapArgString = "tonemapx=tonemap={0}:desat={1}:peak={2}:t=bt709:m=bt709:p=bt709:format={3}";
 3872
 03873                if (options.TonemappingParam != 0)
 3874                {
 03875                    tonemapArgString += ":param={4}";
 3876                }
 3877
 03878                var range = options.TonemappingRange;
 03879                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3880                {
 03881                    tonemapArgString += ":range={5}";
 3882                }
 3883
 03884                var tonemapArgs = string.Format(
 03885                    CultureInfo.InvariantCulture,
 03886                    tonemapArgString,
 03887                    options.TonemappingAlgorithm,
 03888                    options.TonemappingDesat,
 03889                    options.TonemappingPeak,
 03890                    tonemapFormat,
 03891                    options.TonemappingParam,
 03892                    options.TonemappingRange);
 3893
 03894                mainFilters.Add(tonemapArgs);
 3895            }
 3896            else
 3897            {
 3898                // OUTPUT yuv420p/nv12 surface(memory)
 03899                mainFilters.Add("format=" + outFormat);
 3900            }
 3901
 3902            /* Make sub and overlay filters for subtitle stream */
 03903            var subFilters = new List<string>();
 03904            var overlayFilters = new List<string>();
 03905            if (hasTextSubs)
 3906            {
 3907                // subtitles=f='*.ass':alpha=0
 03908                var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03909                mainFilters.Add(textSubtitlesFilter);
 3910            }
 03911            else if (hasGraphicalSubs)
 3912            {
 03913                var subW = state.SubtitleStream?.Width;
 03914                var subH = state.SubtitleStream?.Height;
 03915                var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW
 03916                subFilters.Add(subPreProcFilters);
 03917                overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 3918            }
 3919
 03920            return (mainFilters, subFilters, overlayFilters);
 3921        }
 3922
 3923        /// <summary>
 3924        /// Gets the parameter of Nvidia NVENC filter chain.
 3925        /// </summary>
 3926        /// <param name="state">Encoding state.</param>
 3927        /// <param name="options">Encoding options.</param>
 3928        /// <param name="vidEncoder">Video encoder to use.</param>
 3929        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3930        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFilterChain(
 3931            EncodingJobInfo state,
 3932            EncodingOptions options,
 3933            string vidEncoder)
 3934        {
 03935            if (options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 3936            {
 03937                return (null, null, null);
 3938            }
 3939
 03940            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03941            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03942            var isSwEncoder = !vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 3943
 3944            // legacy cuvid pipeline(copy-back)
 03945            if ((isSwDecoder && isSwEncoder)
 03946                || !IsCudaFullSupported()
 03947                || !_mediaEncoder.SupportsFilter("alphasrc"))
 3948            {
 03949                return GetSwVidFilterChain(state, options, vidEncoder);
 3950            }
 3951
 3952            // preferred nvdec/cuvid + cuda filters + nvenc pipeline
 03953            return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 3954        }
 3955
 3956        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFiltersPrefe
 3957            EncodingJobInfo state,
 3958            EncodingOptions options,
 3959            string vidDecoder,
 3960            string vidEncoder)
 3961        {
 03962            var inW = state.VideoStream?.Width;
 03963            var inH = state.VideoStream?.Height;
 03964            var reqW = state.BaseRequest.Width;
 03965            var reqH = state.BaseRequest.Height;
 03966            var reqMaxW = state.BaseRequest.MaxWidth;
 03967            var reqMaxH = state.BaseRequest.MaxHeight;
 03968            var threeDFormat = state.MediaSource.Video3DFormat;
 3969
 03970            var isNvDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 03971            var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 03972            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03973            var isSwEncoder = !isNvencEncoder;
 03974            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 03975            var isCuInCuOut = isNvDecoder && isNvencEncoder;
 3976
 03977            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03978            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03979            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03980            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03981            var doCuTonemap = IsHwTonemapAvailable(state, options);
 3982
 03983            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03984            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03985            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 03986            var hasAssSubs = hasSubs
 03987                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 03988                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 03989            var subW = state.SubtitleStream?.Width;
 03990            var subH = state.SubtitleStream?.Height;
 3991
 03992            var rotation = state.VideoStream?.Rotation ?? 0;
 03993            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 03994            var doCuTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_cuda");
 03995            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isNvDecoder && doCuTranspose));
 03996            var swpInW = swapWAndH ? inH : inW;
 03997            var swpInH = swapWAndH ? inW : inH;
 3998
 3999            /* Make main filters for video stream */
 04000            var mainFilters = new List<string>();
 4001
 04002            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doCuTonemap));
 4003
 04004            if (isSwDecoder)
 4005            {
 4006                // INPUT sw surface(memory)
 4007                // sw deint
 04008                if (doDeintH2645)
 4009                {
 04010                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04011                    mainFilters.Add(swDeintFilter);
 4012                }
 4013
 04014                var outFormat = doCuTonemap ? "yuv420p10le" : "yuv420p";
 04015                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 4016                // sw scale
 04017                mainFilters.Add(swScaleFilter);
 04018                mainFilters.Add($"format={outFormat}");
 4019
 4020                // sw => hw
 04021                if (doCuTonemap)
 4022                {
 04023                    mainFilters.Add("hwupload=derive_device=cuda");
 4024                }
 4025            }
 4026
 04027            if (isNvDecoder)
 4028            {
 4029                // INPUT cuda surface(vram)
 4030                // hw deint
 04031                if (doDeintH2645)
 4032                {
 04033                    var deintFilter = GetHwDeinterlaceFilter(state, options, "cuda");
 04034                    mainFilters.Add(deintFilter);
 4035                }
 4036
 4037                // hw transpose
 04038                if (doCuTranspose)
 4039                {
 04040                    mainFilters.Add($"transpose_cuda=dir={transposeDir}");
 4041                }
 4042
 04043                var isRext = IsVideoStreamHevcRext(state);
 04044                var outFormat = doCuTonemap ? (isRext ? "p010" : string.Empty) : "yuv420p";
 04045                var hwScaleFilter = GetHwScaleFilter("scale", "cuda", outFormat, false, swpInW, swpInH, reqW, reqH, reqM
 4046                // hw scale
 04047                mainFilters.Add(hwScaleFilter);
 4048            }
 4049
 4050            // hw tonemap
 04051            if (doCuTonemap)
 4052            {
 04053                var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p", isMjpegEncoder);
 04054                mainFilters.Add(tonemapFilter);
 4055            }
 4056
 04057            var memoryOutput = false;
 04058            var isUploadForCuTonemap = isSwDecoder && doCuTonemap;
 04059            if ((isNvDecoder && isSwEncoder) || (isUploadForCuTonemap && hasSubs))
 4060            {
 04061                memoryOutput = true;
 4062
 4063                // OUTPUT yuv420p surface(memory)
 04064                mainFilters.Add("hwdownload");
 04065                mainFilters.Add("format=yuv420p");
 4066            }
 4067
 4068            // OUTPUT yuv420p surface(memory)
 04069            if (isSwDecoder && isNvencEncoder && !isUploadForCuTonemap)
 4070            {
 04071                memoryOutput = true;
 4072            }
 4073
 04074            if (memoryOutput)
 4075            {
 4076                // text subtitles
 04077                if (hasTextSubs)
 4078                {
 04079                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04080                    mainFilters.Add(textSubtitlesFilter);
 4081                }
 4082            }
 4083
 4084            // OUTPUT cuda(yuv420p) surface(vram)
 4085
 4086            /* Make sub and overlay filters for subtitle stream */
 04087            var subFilters = new List<string>();
 04088            var overlayFilters = new List<string>();
 04089            if (isCuInCuOut)
 4090            {
 04091                if (hasSubs)
 4092                {
 04093                    var alphaFormatOpt = string.Empty;
 04094                    if (hasGraphicalSubs)
 4095                    {
 04096                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04097                        subFilters.Add(subPreProcFilters);
 04098                        subFilters.Add("format=yuva420p");
 4099                    }
 04100                    else if (hasTextSubs)
 4101                    {
 04102                        var framerate = state.VideoStream?.RealFrameRate;
 04103                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4104
 4105                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04106                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04107                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04108                        subFilters.Add(alphaSrcFilter);
 04109                        subFilters.Add("format=yuva420p");
 04110                        subFilters.Add(subTextSubtitlesFilter);
 4111
 04112                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayCudaAlphaFormat)
 04113                            ? ":alpha_format=premultiplied" : string.Empty;
 4114                    }
 4115
 04116                    subFilters.Add("hwupload=derive_device=cuda");
 04117                    overlayFilters.Add($"overlay_cuda=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 4118                }
 4119            }
 4120            else
 4121            {
 04122                if (hasGraphicalSubs)
 4123                {
 04124                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04125                    subFilters.Add(subPreProcFilters);
 04126                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4127                }
 4128            }
 4129
 04130            return (mainFilters, subFilters, overlayFilters);
 4131        }
 4132
 4133        /// <summary>
 4134        /// Gets the parameter of AMD AMF filter chain.
 4135        /// </summary>
 4136        /// <param name="state">Encoding state.</param>
 4137        /// <param name="options">Encoding options.</param>
 4138        /// <param name="vidEncoder">Video encoder to use.</param>
 4139        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4140        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVidFilterChain(
 4141            EncodingJobInfo state,
 4142            EncodingOptions options,
 4143            string vidEncoder)
 4144        {
 04145            if (options.HardwareAccelerationType != HardwareAccelerationType.amf)
 4146            {
 04147                return (null, null, null);
 4148            }
 4149
 04150            var isWindows = OperatingSystem.IsWindows();
 04151            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04152            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04153            var isSwEncoder = !vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04154            var isAmfDx11OclSupported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va") && IsOpenclFullSupported()
 4155
 4156            // legacy d3d11va pipeline(copy-back)
 04157            if ((isSwDecoder && isSwEncoder)
 04158                || !isAmfDx11OclSupported
 04159                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4160            {
 04161                return GetSwVidFilterChain(state, options, vidEncoder);
 4162            }
 4163
 4164            // preferred d3d11va + opencl filters + amf pipeline
 04165            return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4166        }
 4167
 4168        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdDx11VidFiltersPref
 4169            EncodingJobInfo state,
 4170            EncodingOptions options,
 4171            string vidDecoder,
 4172            string vidEncoder)
 4173        {
 04174            var inW = state.VideoStream?.Width;
 04175            var inH = state.VideoStream?.Height;
 04176            var reqW = state.BaseRequest.Width;
 04177            var reqH = state.BaseRequest.Height;
 04178            var reqMaxW = state.BaseRequest.MaxWidth;
 04179            var reqMaxH = state.BaseRequest.MaxHeight;
 04180            var threeDFormat = state.MediaSource.Video3DFormat;
 4181
 04182            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04183            var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04184            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04185            var isSwEncoder = !isAmfEncoder;
 04186            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04187            var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder;
 4188
 04189            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04190            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04191            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04192            var doOclTonemap = IsHwTonemapAvailable(state, options);
 4193
 04194            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04195            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04196            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04197            var hasAssSubs = hasSubs
 04198                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04199                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04200            var subW = state.SubtitleStream?.Width;
 04201            var subH = state.SubtitleStream?.Height;
 4202
 04203            var rotation = state.VideoStream?.Rotation ?? 0;
 04204            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04205            var doOclTranspose = !string.IsNullOrEmpty(transposeDir)
 04206                && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TransposeOpenclReversal);
 04207            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isD3d11vaDecoder && doOclTranspose));
 04208            var swpInW = swapWAndH ? inH : inW;
 04209            var swpInH = swapWAndH ? inW : inH;
 4210
 4211            /* Make main filters for video stream */
 04212            var mainFilters = new List<string>();
 4213
 04214            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 4215
 04216            if (isSwDecoder)
 4217            {
 4218                // INPUT sw surface(memory)
 4219                // sw deint
 04220                if (doDeintH2645)
 4221                {
 04222                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04223                    mainFilters.Add(swDeintFilter);
 4224                }
 4225
 04226                var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
 04227                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 4228                // sw scale
 04229                mainFilters.Add(swScaleFilter);
 04230                mainFilters.Add($"format={outFormat}");
 4231
 4232                // keep video at memory except ocl tonemap,
 4233                // since the overhead caused by hwupload >>> using sw filter.
 4234                // sw => hw
 04235                if (doOclTonemap)
 4236                {
 04237                    mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=24");
 04238                    mainFilters.Add("format=d3d11");
 04239                    mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4240                }
 4241            }
 4242
 04243            if (isD3d11vaDecoder)
 4244            {
 4245                // INPUT d3d11 surface(vram)
 4246                // map from d3d11va to opencl via d3d11-opencl interop.
 04247                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4248
 4249                // hw deint
 04250                if (doDeintH2645)
 4251                {
 04252                    var deintFilter = GetHwDeinterlaceFilter(state, options, "opencl");
 04253                    mainFilters.Add(deintFilter);
 4254                }
 4255
 4256                // hw transpose
 04257                if (doOclTranspose)
 4258                {
 04259                    mainFilters.Add($"transpose_opencl=dir={transposeDir}");
 4260                }
 4261
 04262                var outFormat = doOclTonemap ? string.Empty : "nv12";
 04263                var hwScaleFilter = GetHwScaleFilter("scale", "opencl", outFormat, false, swpInW, swpInH, reqW, reqH, re
 4264                // hw scale
 04265                mainFilters.Add(hwScaleFilter);
 4266            }
 4267
 4268            // hw tonemap
 04269            if (doOclTonemap)
 4270            {
 04271                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04272                mainFilters.Add(tonemapFilter);
 4273            }
 4274
 04275            var memoryOutput = false;
 04276            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04277            if (isD3d11vaDecoder && isSwEncoder)
 4278            {
 04279                memoryOutput = true;
 4280
 4281                // OUTPUT nv12 surface(memory)
 4282                // prefer hwmap to hwdownload on opencl.
 04283                var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap=mode=read";
 04284                mainFilters.Add(hwTransferFilter);
 04285                mainFilters.Add("format=nv12");
 4286            }
 4287
 4288            // OUTPUT yuv420p surface
 04289            if (isSwDecoder && isAmfEncoder && !isUploadForOclTonemap)
 4290            {
 04291                memoryOutput = true;
 4292            }
 4293
 04294            if (memoryOutput)
 4295            {
 4296                // text subtitles
 04297                if (hasTextSubs)
 4298                {
 04299                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04300                    mainFilters.Add(textSubtitlesFilter);
 4301                }
 4302            }
 4303
 04304            if ((isDxInDxOut || isUploadForOclTonemap) && !hasSubs)
 4305            {
 4306                // OUTPUT d3d11(nv12) surface(vram)
 4307                // reverse-mapping via d3d11-opencl interop.
 04308                mainFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04309                mainFilters.Add("format=d3d11");
 4310            }
 4311
 4312            /* Make sub and overlay filters for subtitle stream */
 04313            var subFilters = new List<string>();
 04314            var overlayFilters = new List<string>();
 04315            if (isDxInDxOut || isUploadForOclTonemap)
 4316            {
 04317                if (hasSubs)
 4318                {
 04319                    var alphaFormatOpt = string.Empty;
 04320                    if (hasGraphicalSubs)
 4321                    {
 04322                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04323                        subFilters.Add(subPreProcFilters);
 04324                        subFilters.Add("format=yuva420p");
 4325                    }
 04326                    else if (hasTextSubs)
 4327                    {
 04328                        var framerate = state.VideoStream?.RealFrameRate;
 04329                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4330
 4331                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04332                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04333                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04334                        subFilters.Add(alphaSrcFilter);
 04335                        subFilters.Add("format=yuva420p");
 04336                        subFilters.Add(subTextSubtitlesFilter);
 4337
 04338                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclAlphaForma
 04339                            ? ":alpha_format=premultiplied" : string.Empty;
 4340                    }
 4341
 04342                    subFilters.Add("hwupload=derive_device=opencl");
 04343                    overlayFilters.Add($"overlay_opencl=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 04344                    overlayFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04345                    overlayFilters.Add("format=d3d11");
 4346                }
 4347            }
 04348            else if (memoryOutput)
 4349            {
 04350                if (hasGraphicalSubs)
 4351                {
 04352                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04353                    subFilters.Add(subPreProcFilters);
 04354                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4355                }
 4356            }
 4357
 04358            return (mainFilters, subFilters, overlayFilters);
 4359        }
 4360
 4361        /// <summary>
 4362        /// Gets the parameter of Intel QSV filter chain.
 4363        /// </summary>
 4364        /// <param name="state">Encoding state.</param>
 4365        /// <param name="options">Encoding options.</param>
 4366        /// <param name="vidEncoder">Video encoder to use.</param>
 4367        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4368        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVidFilterChain(
 4369            EncodingJobInfo state,
 4370            EncodingOptions options,
 4371            string vidEncoder)
 4372        {
 04373            if (options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 4374            {
 04375                return (null, null, null);
 4376            }
 4377
 04378            var isWindows = OperatingSystem.IsWindows();
 04379            var isLinux = OperatingSystem.IsLinux();
 04380            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04381            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04382            var isSwEncoder = !vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04383            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 04384            var isIntelDx11OclSupported = isWindows
 04385                && _mediaEncoder.SupportsHwaccel("d3d11va")
 04386                && isQsvOclSupported;
 04387            var isIntelVaapiOclSupported = isLinux
 04388                && IsVaapiSupported(state)
 04389                && isQsvOclSupported;
 4390
 4391            // legacy qsv pipeline(copy-back)
 04392            if ((isSwDecoder && isSwEncoder)
 04393                || (!isIntelVaapiOclSupported && !isIntelDx11OclSupported)
 04394                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4395            {
 04396                return GetSwVidFilterChain(state, options, vidEncoder);
 4397            }
 4398
 4399            // preferred qsv(vaapi) + opencl filters pipeline
 04400            if (isIntelVaapiOclSupported)
 4401            {
 04402                return GetIntelQsvVaapiVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4403            }
 4404
 4405            // preferred qsv(d3d11) + opencl filters pipeline
 04406            if (isIntelDx11OclSupported)
 4407            {
 04408                return GetIntelQsvDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4409            }
 4410
 04411            return (null, null, null);
 4412        }
 4413
 4414        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvDx11VidFilter
 4415            EncodingJobInfo state,
 4416            EncodingOptions options,
 4417            string vidDecoder,
 4418            string vidEncoder)
 4419        {
 04420            var inW = state.VideoStream?.Width;
 04421            var inH = state.VideoStream?.Height;
 04422            var reqW = state.BaseRequest.Width;
 04423            var reqH = state.BaseRequest.Height;
 04424            var reqMaxW = state.BaseRequest.MaxWidth;
 04425            var reqMaxH = state.BaseRequest.MaxHeight;
 04426            var threeDFormat = state.MediaSource.Video3DFormat;
 4427
 04428            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04429            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04430            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04431            var isHwDecoder = isD3d11vaDecoder || isQsvDecoder;
 04432            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04433            var isSwEncoder = !isQsvEncoder;
 04434            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04435            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4436
 04437            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04438            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04439            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04440            var doVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04441            var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options);
 04442            var doTonemap = doVppTonemap || doOclTonemap;
 4443
 04444            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04445            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04446            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04447            var hasAssSubs = hasSubs
 04448                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04449                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04450            var subW = state.SubtitleStream?.Width;
 04451            var subH = state.SubtitleStream?.Height;
 4452
 04453            var rotation = state.VideoStream?.Rotation ?? 0;
 04454            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04455            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04456            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isD3d11vaDecoder || isQsvDecoder) && doVppTran
 04457            var swpInW = swapWAndH ? inH : inW;
 04458            var swpInH = swapWAndH ? inW : inH;
 4459
 4460            /* Make main filters for video stream */
 04461            var mainFilters = new List<string>();
 4462
 04463            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4464
 04465            if (isSwDecoder)
 4466            {
 4467                // INPUT sw surface(memory)
 4468                // sw deint
 04469                if (doDeintH2645)
 4470                {
 04471                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04472                    mainFilters.Add(swDeintFilter);
 4473                }
 4474
 04475                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04476                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04477                if (isMjpegEncoder && !doOclTonemap)
 4478                {
 4479                    // sw decoder + hw mjpeg encoder
 04480                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4481                }
 4482
 4483                // sw scale
 04484                mainFilters.Add(swScaleFilter);
 04485                mainFilters.Add($"format={outFormat}");
 4486
 4487                // keep video at memory except ocl tonemap,
 4488                // since the overhead caused by hwupload >>> using sw filter.
 4489                // sw => hw
 04490                if (doOclTonemap)
 4491                {
 04492                    mainFilters.Add("hwupload=derive_device=opencl");
 4493                }
 4494            }
 04495            else if (isD3d11vaDecoder || isQsvDecoder)
 4496            {
 04497                var isRext = IsVideoStreamHevcRext(state);
 04498                var twoPassVppTonemap = false;
 04499                var doVppFullRangeOut = isMjpegEncoder
 04500                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04501                var doVppScaleModeHq = isMjpegEncoder
 04502                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 04503                var doVppProcamp = false;
 04504                var procampParams = string.Empty;
 04505                var procampParamsString = string.Empty;
 04506                if (doVppTonemap)
 4507                {
 04508                    if (isRext)
 4509                    {
 4510                        // VPP tonemap requires p010 input
 04511                        twoPassVppTonemap = true;
 4512                    }
 4513
 04514                    if (options.VppTonemappingBrightness != 0
 04515                        && options.VppTonemappingBrightness >= -100
 04516                        && options.VppTonemappingBrightness <= 100)
 4517                    {
 04518                        procampParamsString += ":brightness={0}";
 04519                        twoPassVppTonemap = doVppProcamp = true;
 4520                    }
 4521
 04522                    if (options.VppTonemappingContrast > 1
 04523                        && options.VppTonemappingContrast <= 10)
 4524                    {
 04525                        procampParamsString += ":contrast={1}";
 04526                        twoPassVppTonemap = doVppProcamp = true;
 4527                    }
 4528
 04529                    if (doVppProcamp)
 4530                    {
 04531                        procampParamsString += ":procamp=1:async_depth=2";
 04532                        procampParams = string.Format(
 04533                            CultureInfo.InvariantCulture,
 04534                            procampParamsString,
 04535                            options.VppTonemappingBrightness,
 04536                            options.VppTonemappingContrast);
 4537                    }
 4538                }
 4539
 04540                var outFormat = doOclTonemap ? ((doVppTranspose || isRext) ? "p010" : string.Empty) : "nv12";
 04541                outFormat = twoPassVppTonemap ? "p010" : outFormat;
 4542
 04543                var swapOutputWandH = doVppTranspose && swapWAndH;
 04544                var hwScaleFilter = GetHwScaleFilter("vpp", "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, req
 4545
 4546                // d3d11va doesn't support dynamic pool size, use vpp filter ctx to relay
 4547                // to prevent encoder async and bframes from exhausting the decoder pool.
 04548                if (!string.IsNullOrEmpty(hwScaleFilter) && isD3d11vaDecoder)
 4549                {
 04550                    hwScaleFilter += ":passthrough=0";
 4551                }
 4552
 04553                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose)
 4554                {
 04555                    hwScaleFilter += $":transpose={transposeDir}";
 4556                }
 4557
 04558                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4559                {
 04560                    hwScaleFilter += (doVppFullRangeOut && !doOclTonemap) ? ":out_range=pc" : string.Empty;
 04561                    hwScaleFilter += doVppScaleModeHq ? ":scale_mode=hq" : string.Empty;
 4562                }
 4563
 04564                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTonemap)
 4565                {
 04566                    hwScaleFilter += doVppProcamp ? procampParams : (twoPassVppTonemap ? string.Empty : ":tonemap=1");
 4567                }
 4568
 04569                if (isD3d11vaDecoder)
 4570                {
 04571                    if (!string.IsNullOrEmpty(hwScaleFilter) || doDeintH2645)
 4572                    {
 4573                        // INPUT d3d11 surface(vram)
 4574                        // map from d3d11va to qsv.
 04575                        mainFilters.Add("hwmap=derive_device=qsv");
 4576                    }
 4577                }
 4578
 4579                // hw deint
 04580                if (doDeintH2645)
 4581                {
 04582                    var deintFilter = GetHwDeinterlaceFilter(state, options, "qsv");
 04583                    mainFilters.Add(deintFilter);
 4584                }
 4585
 4586                // hw transpose & scale & tonemap(w/o procamp)
 04587                mainFilters.Add(hwScaleFilter);
 4588
 4589                // hw tonemap(w/ procamp)
 04590                if (doVppTonemap && twoPassVppTonemap)
 4591                {
 04592                    mainFilters.Add("vpp_qsv=tonemap=1:format=nv12:async_depth=2");
 4593                }
 4594
 4595                // force bt709 just in case vpp tonemap is not triggered or using MSDK instead of VPL.
 04596                if (doVppTonemap)
 4597                {
 04598                    mainFilters.Add(GetOverwriteColorPropertiesParam(state, false));
 4599                }
 4600            }
 4601
 04602            if (doOclTonemap && isHwDecoder)
 4603            {
 4604                // map from qsv to opencl via qsv(d3d11)-opencl interop.
 04605                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4606            }
 4607
 4608            // hw tonemap
 04609            if (doOclTonemap)
 4610            {
 04611                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04612                mainFilters.Add(tonemapFilter);
 4613            }
 4614
 04615            var memoryOutput = false;
 04616            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04617            var isHwmapUsable = isSwEncoder && doOclTonemap;
 04618            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4619            {
 04620                memoryOutput = true;
 4621
 4622                // OUTPUT nv12 surface(memory)
 4623                // prefer hwmap to hwdownload on opencl.
 4624                // qsv hwmap is not fully implemented for the time being.
 04625                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04626                mainFilters.Add("format=nv12");
 4627            }
 4628
 4629            // OUTPUT nv12 surface(memory)
 04630            if (isSwDecoder && isQsvEncoder)
 4631            {
 04632                memoryOutput = true;
 4633            }
 4634
 04635            if (memoryOutput)
 4636            {
 4637                // text subtitles
 04638                if (hasTextSubs)
 4639                {
 04640                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04641                    mainFilters.Add(textSubtitlesFilter);
 4642                }
 4643            }
 4644
 04645            if (isQsvInQsvOut && doOclTonemap)
 4646            {
 4647                // OUTPUT qsv(nv12) surface(vram)
 4648                // reverse-mapping via qsv(d3d11)-opencl interop.
 04649                mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1");
 04650                mainFilters.Add("format=qsv");
 4651            }
 4652
 4653            /* Make sub and overlay filters for subtitle stream */
 04654            var subFilters = new List<string>();
 04655            var overlayFilters = new List<string>();
 04656            if (isQsvInQsvOut)
 4657            {
 04658                if (hasSubs)
 4659                {
 04660                    if (hasGraphicalSubs)
 4661                    {
 4662                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04663                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04664                        subFilters.Add(subPreProcFilters);
 04665                        subFilters.Add("format=bgra");
 4666                    }
 04667                    else if (hasTextSubs)
 4668                    {
 04669                        var framerate = state.VideoStream?.RealFrameRate;
 04670                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4671
 4672                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 04673                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04674                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04675                        subFilters.Add(alphaSrcFilter);
 04676                        subFilters.Add("format=bgra");
 04677                        subFilters.Add(subTextSubtitlesFilter);
 4678                    }
 4679
 4680                    // qsv requires a fixed pool size.
 4681                    // default to 64 otherwise it will fail on certain iGPU.
 04682                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4683
 04684                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04685                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04686                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04687                        : string.Empty;
 04688                    var overlayQsvFilter = string.Format(
 04689                        CultureInfo.InvariantCulture,
 04690                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04691                        overlaySize);
 04692                    overlayFilters.Add(overlayQsvFilter);
 4693                }
 4694            }
 04695            else if (memoryOutput)
 4696            {
 04697                if (hasGraphicalSubs)
 4698                {
 04699                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04700                    subFilters.Add(subPreProcFilters);
 04701                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4702                }
 4703            }
 4704
 04705            return (mainFilters, subFilters, overlayFilters);
 4706        }
 4707
 4708        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvVaapiVidFilte
 4709            EncodingJobInfo state,
 4710            EncodingOptions options,
 4711            string vidDecoder,
 4712            string vidEncoder)
 4713        {
 04714            var inW = state.VideoStream?.Width;
 04715            var inH = state.VideoStream?.Height;
 04716            var reqW = state.BaseRequest.Width;
 04717            var reqH = state.BaseRequest.Height;
 04718            var reqMaxW = state.BaseRequest.MaxWidth;
 04719            var reqMaxH = state.BaseRequest.MaxHeight;
 04720            var threeDFormat = state.MediaSource.Video3DFormat;
 4721
 04722            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04723            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04724            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04725            var isHwDecoder = isVaapiDecoder || isQsvDecoder;
 04726            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04727            var isSwEncoder = !isQsvEncoder;
 04728            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04729            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4730
 04731            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04732            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04733            var doVaVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04734            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04735            var doTonemap = doVaVppTonemap || doOclTonemap;
 04736            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4737
 04738            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04739            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04740            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04741            var hasAssSubs = hasSubs
 04742                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04743                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04744            var subW = state.SubtitleStream?.Width;
 04745            var subH = state.SubtitleStream?.Height;
 4746
 04747            var rotation = state.VideoStream?.Rotation ?? 0;
 04748            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04749            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04750            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isVaapiDecoder || isQsvDecoder) && doVppTransp
 04751            var swpInW = swapWAndH ? inH : inW;
 04752            var swpInH = swapWAndH ? inW : inH;
 4753
 4754            /* Make main filters for video stream */
 04755            var mainFilters = new List<string>();
 4756
 04757            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4758
 04759            if (isSwDecoder)
 4760            {
 4761                // INPUT sw surface(memory)
 4762                // sw deint
 04763                if (doDeintH2645)
 4764                {
 04765                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04766                    mainFilters.Add(swDeintFilter);
 4767                }
 4768
 04769                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04770                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04771                if (isMjpegEncoder && !doOclTonemap)
 4772                {
 4773                    // sw decoder + hw mjpeg encoder
 04774                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4775                }
 4776
 4777                // sw scale
 04778                mainFilters.Add(swScaleFilter);
 04779                mainFilters.Add($"format={outFormat}");
 4780
 4781                // keep video at memory except ocl tonemap,
 4782                // since the overhead caused by hwupload >>> using sw filter.
 4783                // sw => hw
 04784                if (doOclTonemap)
 4785                {
 04786                    mainFilters.Add("hwupload=derive_device=opencl");
 4787                }
 4788            }
 04789            else if (isVaapiDecoder || isQsvDecoder)
 4790            {
 04791                var hwFilterSuffix = isVaapiDecoder ? "vaapi" : "qsv";
 04792                var isRext = IsVideoStreamHevcRext(state);
 04793                var doVppFullRangeOut = isMjpegEncoder
 04794                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04795                var doVppScaleModeHq = isMjpegEncoder
 04796                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 4797
 4798                // INPUT vaapi/qsv surface(vram)
 4799                // hw deint
 04800                if (doDeintH2645)
 4801                {
 04802                    var deintFilter = GetHwDeinterlaceFilter(state, options, hwFilterSuffix);
 04803                    mainFilters.Add(deintFilter);
 4804                }
 4805
 4806                // hw transpose(vaapi vpp)
 04807                if (isVaapiDecoder && doVppTranspose)
 4808                {
 04809                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 4810                }
 4811
 04812                var outFormat = doTonemap ? (((isQsvDecoder && doVppTranspose) || isRext) ? "p010" : string.Empty) : "nv
 04813                var swapOutputWandH = isQsvDecoder && doVppTranspose && swapWAndH;
 04814                var hwScalePrefix = isQsvDecoder ? "vpp" : "scale";
 04815                var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, hwFilterSuffix, outFormat, swapOutputWandH, swpInW, 
 4816
 04817                if (!string.IsNullOrEmpty(hwScaleFilter) && isQsvDecoder && doVppTranspose)
 4818                {
 04819                    hwScaleFilter += $":transpose={transposeDir}";
 4820                }
 4821
 04822                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4823                {
 04824                    hwScaleFilter += ((isQsvDecoder && !doVppFullRangeOut) || doOclTonemap) ? string.Empty : ":out_range
 04825                    hwScaleFilter += isQsvDecoder ? (doVppScaleModeHq ? ":scale_mode=hq" : string.Empty) : ":mode=hq";
 4826                }
 4827
 4828                // allocate extra pool sizes for vaapi vpp scale
 04829                if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder)
 4830                {
 04831                    hwScaleFilter += ":extra_hw_frames=24";
 4832                }
 4833
 4834                // hw transpose(qsv vpp) & scale
 04835                mainFilters.Add(hwScaleFilter);
 4836            }
 4837
 4838            // vaapi vpp tonemap
 04839            if (doVaVppTonemap && isHwDecoder)
 4840            {
 04841                if (isQsvDecoder)
 4842                {
 4843                    // map from qsv to vaapi.
 04844                    mainFilters.Add("hwmap=derive_device=vaapi");
 04845                    mainFilters.Add("format=vaapi");
 4846                }
 4847
 04848                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 04849                mainFilters.Add(tonemapFilter);
 4850
 04851                if (isQsvDecoder)
 4852                {
 4853                    // map from vaapi to qsv.
 04854                    mainFilters.Add("hwmap=derive_device=qsv");
 04855                    mainFilters.Add("format=qsv");
 4856                }
 4857            }
 4858
 04859            if (doOclTonemap && isHwDecoder)
 4860            {
 4861                // map from qsv to opencl via qsv(vaapi)-opencl interop.
 04862                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4863            }
 4864
 4865            // ocl tonemap
 04866            if (doOclTonemap)
 4867            {
 04868                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04869                mainFilters.Add(tonemapFilter);
 4870            }
 4871
 04872            var memoryOutput = false;
 04873            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04874            var isHwmapUsable = isSwEncoder && (doOclTonemap || isVaapiDecoder);
 04875            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4876            {
 04877                memoryOutput = true;
 4878
 4879                // OUTPUT nv12 surface(memory)
 4880                // prefer hwmap to hwdownload on opencl/vaapi.
 4881                // qsv hwmap is not fully implemented for the time being.
 04882                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04883                mainFilters.Add("format=nv12");
 4884            }
 4885
 4886            // OUTPUT nv12 surface(memory)
 04887            if (isSwDecoder && isQsvEncoder)
 4888            {
 04889                memoryOutput = true;
 4890            }
 4891
 04892            if (memoryOutput)
 4893            {
 4894                // text subtitles
 04895                if (hasTextSubs)
 4896                {
 04897                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04898                    mainFilters.Add(textSubtitlesFilter);
 4899                }
 4900            }
 4901
 04902            if (isQsvInQsvOut)
 4903            {
 04904                if (doOclTonemap)
 4905                {
 4906                    // OUTPUT qsv(nv12) surface(vram)
 4907                    // reverse-mapping via qsv(vaapi)-opencl interop.
 4908                    // add extra pool size to avoid the 'cannot allocate memory' error on hevc_qsv.
 04909                    mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1:extra_hw_frames=16");
 04910                    mainFilters.Add("format=qsv");
 4911                }
 04912                else if (isVaapiDecoder)
 4913                {
 04914                    mainFilters.Add("hwmap=derive_device=qsv");
 04915                    mainFilters.Add("format=qsv");
 4916                }
 4917            }
 4918
 4919            /* Make sub and overlay filters for subtitle stream */
 04920            var subFilters = new List<string>();
 04921            var overlayFilters = new List<string>();
 04922            if (isQsvInQsvOut)
 4923            {
 04924                if (hasSubs)
 4925                {
 04926                    if (hasGraphicalSubs)
 4927                    {
 4928                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04929                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04930                        subFilters.Add(subPreProcFilters);
 04931                        subFilters.Add("format=bgra");
 4932                    }
 04933                    else if (hasTextSubs)
 4934                    {
 04935                        var framerate = state.VideoStream?.RealFrameRate;
 04936                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4937
 04938                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04939                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04940                        subFilters.Add(alphaSrcFilter);
 04941                        subFilters.Add("format=bgra");
 04942                        subFilters.Add(subTextSubtitlesFilter);
 4943                    }
 4944
 4945                    // qsv requires a fixed pool size.
 4946                    // default to 64 otherwise it will fail on certain iGPU.
 04947                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4948
 04949                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04950                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04951                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04952                        : string.Empty;
 04953                    var overlayQsvFilter = string.Format(
 04954                        CultureInfo.InvariantCulture,
 04955                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04956                        overlaySize);
 04957                    overlayFilters.Add(overlayQsvFilter);
 4958                }
 4959            }
 04960            else if (memoryOutput)
 4961            {
 04962                if (hasGraphicalSubs)
 4963                {
 04964                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04965                    subFilters.Add(subPreProcFilters);
 04966                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4967                }
 4968            }
 4969
 04970            return (mainFilters, subFilters, overlayFilters);
 4971        }
 4972
 4973        /// <summary>
 4974        /// Gets the parameter of Intel/AMD VAAPI filter chain.
 4975        /// </summary>
 4976        /// <param name="state">Encoding state.</param>
 4977        /// <param name="options">Encoding options.</param>
 4978        /// <param name="vidEncoder">Video encoder to use.</param>
 4979        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4980        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiVidFilterChain(
 4981            EncodingJobInfo state,
 4982            EncodingOptions options,
 4983            string vidEncoder)
 4984        {
 04985            if (options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 4986            {
 04987                return (null, null, null);
 4988            }
 4989
 04990            var isLinux = OperatingSystem.IsLinux();
 04991            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04992            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04993            var isSwEncoder = !vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04994            var isVaapiFullSupported = isLinux && IsVaapiSupported(state) && IsVaapiFullSupported();
 04995            var isVaapiOclSupported = isVaapiFullSupported && IsOpenclFullSupported();
 04996            var isVaapiVkSupported = isVaapiFullSupported && IsVulkanFullSupported();
 4997
 4998            // legacy vaapi pipeline(copy-back)
 04999            if ((isSwDecoder && isSwEncoder)
 05000                || !isVaapiOclSupported
 05001                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5002            {
 05003                var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
 5004
 05005                if (!isSwEncoder)
 5006                {
 05007                    var newfilters = new List<string>();
 05008                    var noOverlay = swFilterChain.OverlayFilters.Count == 0;
 05009                    newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
 05010                    newfilters.Add("hwupload=derive_device=vaapi");
 5011
 05012                    var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
 05013                    var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
 05014                    return (mainFilters, swFilterChain.SubFilters, overlayFilters);
 5015                }
 5016
 05017                return swFilterChain;
 5018            }
 5019
 5020            // preferred vaapi + opencl filters pipeline
 05021            if (_mediaEncoder.IsVaapiDeviceInteliHD)
 5022            {
 5023                // Intel iHD path, with extra vpp tonemap and overlay support.
 05024                return GetIntelVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5025            }
 5026
 5027            // preferred vaapi + vulkan filters pipeline
 05028            if (_mediaEncoder.IsVaapiDeviceAmd
 05029                && isVaapiVkSupported
 05030                && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 05031                && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 5032            {
 5033                // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support.
 05034                return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5035            }
 5036
 5037            // Intel i965 and Amd legacy driver path, only featuring scale and deinterlace support.
 05038            return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5039        }
 5040
 5041        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVaapiFullVidFilt
 5042            EncodingJobInfo state,
 5043            EncodingOptions options,
 5044            string vidDecoder,
 5045            string vidEncoder)
 5046        {
 05047            var inW = state.VideoStream?.Width;
 05048            var inH = state.VideoStream?.Height;
 05049            var reqW = state.BaseRequest.Width;
 05050            var reqH = state.BaseRequest.Height;
 05051            var reqMaxW = state.BaseRequest.MaxWidth;
 05052            var reqMaxH = state.BaseRequest.MaxHeight;
 05053            var threeDFormat = state.MediaSource.Video3DFormat;
 5054
 05055            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05056            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05057            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05058            var isSwEncoder = !isVaapiEncoder;
 05059            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05060            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 5061
 05062            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05063            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05064            var doVaVppTonemap = isVaapiDecoder && IsIntelVppTonemapAvailable(state, options);
 05065            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 05066            var doTonemap = doVaVppTonemap || doOclTonemap;
 05067            var doDeintH2645 = doDeintH264 || doDeintHevc;
 5068
 05069            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05070            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05071            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05072            var hasAssSubs = hasSubs
 05073                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05074                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 05075            var subW = state.SubtitleStream?.Width;
 05076            var subH = state.SubtitleStream?.Height;
 5077
 05078            var rotation = state.VideoStream?.Rotation ?? 0;
 05079            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05080            var doVaVppTranspose = !string.IsNullOrEmpty(transposeDir);
 05081            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVaVppTranspose));
 05082            var swpInW = swapWAndH ? inH : inW;
 05083            var swpInH = swapWAndH ? inW : inH;
 5084
 5085            /* Make main filters for video stream */
 05086            var mainFilters = new List<string>();
 5087
 05088            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 5089
 05090            if (isSwDecoder)
 5091            {
 5092                // INPUT sw surface(memory)
 5093                // sw deint
 05094                if (doDeintH2645)
 5095                {
 05096                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05097                    mainFilters.Add(swDeintFilter);
 5098                }
 5099
 05100                var outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05101                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05102                if (isMjpegEncoder && !doOclTonemap)
 5103                {
 5104                    // sw decoder + hw mjpeg encoder
 05105                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5106                }
 5107
 5108                // sw scale
 05109                mainFilters.Add(swScaleFilter);
 05110                mainFilters.Add($"format={outFormat}");
 5111
 5112                // keep video at memory except ocl tonemap,
 5113                // since the overhead caused by hwupload >>> using sw filter.
 5114                // sw => hw
 05115                if (doOclTonemap)
 5116                {
 05117                    mainFilters.Add("hwupload=derive_device=opencl");
 5118                }
 5119            }
 05120            else if (isVaapiDecoder)
 5121            {
 05122                var isRext = IsVideoStreamHevcRext(state);
 5123
 5124                // INPUT vaapi surface(vram)
 5125                // hw deint
 05126                if (doDeintH2645)
 5127                {
 05128                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05129                    mainFilters.Add(deintFilter);
 5130                }
 5131
 5132                // hw transpose
 05133                if (doVaVppTranspose)
 5134                {
 05135                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 5136                }
 5137
 05138                var outFormat = doTonemap ? (isRext ? "p010" : string.Empty) : "nv12";
 05139                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, swpInW, swpInH, reqW, reqH, req
 5140
 05141                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5142                {
 05143                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05144                    hwScaleFilter += ":mode=hq";
 5145                }
 5146
 5147                // allocate extra pool sizes for vaapi vpp
 05148                if (!string.IsNullOrEmpty(hwScaleFilter))
 5149                {
 05150                    hwScaleFilter += ":extra_hw_frames=24";
 5151                }
 5152
 5153                // hw scale
 05154                mainFilters.Add(hwScaleFilter);
 5155            }
 5156
 5157            // vaapi vpp tonemap
 05158            if (doVaVppTonemap && isVaapiDecoder)
 5159            {
 05160                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 05161                mainFilters.Add(tonemapFilter);
 5162            }
 5163
 05164            if (doOclTonemap && isVaapiDecoder)
 5165            {
 5166                // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05167                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 5168            }
 5169
 5170            // ocl tonemap
 05171            if (doOclTonemap)
 5172            {
 05173                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05174                mainFilters.Add(tonemapFilter);
 5175            }
 5176
 05177            if (doOclTonemap && isVaInVaOut)
 5178            {
 5179                // OUTPUT vaapi(nv12) surface(vram)
 5180                // reverse-mapping via vaapi-opencl interop.
 05181                mainFilters.Add("hwmap=derive_device=vaapi:mode=write:reverse=1");
 05182                mainFilters.Add("format=vaapi");
 5183            }
 5184
 05185            var memoryOutput = false;
 05186            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 05187            var isHwmapNotUsable = isUploadForOclTonemap && isVaapiEncoder;
 05188            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap)
 5189            {
 05190                memoryOutput = true;
 5191
 5192                // OUTPUT nv12 surface(memory)
 5193                // prefer hwmap to hwdownload on opencl/vaapi.
 05194                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap=mode=read");
 05195                mainFilters.Add("format=nv12");
 5196            }
 5197
 5198            // OUTPUT nv12 surface(memory)
 05199            if (isSwDecoder && isVaapiEncoder)
 5200            {
 05201                memoryOutput = true;
 5202            }
 5203
 05204            if (memoryOutput)
 5205            {
 5206                // text subtitles
 05207                if (hasTextSubs)
 5208                {
 05209                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05210                    mainFilters.Add(textSubtitlesFilter);
 5211                }
 5212            }
 5213
 05214            if (memoryOutput && isVaapiEncoder)
 5215            {
 05216                if (!hasGraphicalSubs)
 5217                {
 05218                    mainFilters.Add("hwupload_vaapi");
 5219                }
 5220            }
 5221
 5222            /* Make sub and overlay filters for subtitle stream */
 05223            var subFilters = new List<string>();
 05224            var overlayFilters = new List<string>();
 05225            if (isVaInVaOut)
 5226            {
 05227                if (hasSubs)
 5228                {
 05229                    if (hasGraphicalSubs)
 5230                    {
 5231                        // overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead
 05232                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 05233                        subFilters.Add(subPreProcFilters);
 05234                        subFilters.Add("format=bgra");
 5235                    }
 05236                    else if (hasTextSubs)
 5237                    {
 05238                        var framerate = state.VideoStream?.RealFrameRate;
 05239                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5240
 05241                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 05242                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05243                        subFilters.Add(alphaSrcFilter);
 05244                        subFilters.Add("format=bgra");
 05245                        subFilters.Add(subTextSubtitlesFilter);
 5246                    }
 5247
 05248                    subFilters.Add("hwupload=derive_device=vaapi");
 5249
 05250                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 05251                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 05252                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 05253                        : string.Empty;
 05254                    var overlayVaapiFilter = string.Format(
 05255                        CultureInfo.InvariantCulture,
 05256                        "overlay_vaapi=eof_action=pass:repeatlast=0{0}",
 05257                        overlaySize);
 05258                    overlayFilters.Add(overlayVaapiFilter);
 5259                }
 5260            }
 05261            else if (memoryOutput)
 5262            {
 05263                if (hasGraphicalSubs)
 5264                {
 05265                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05266                    subFilters.Add(subPreProcFilters);
 05267                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5268
 05269                    if (isVaapiEncoder)
 5270                    {
 05271                        overlayFilters.Add("hwupload_vaapi");
 5272                    }
 5273                }
 5274            }
 5275
 05276            return (mainFilters, subFilters, overlayFilters);
 5277        }
 5278
 5279        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVaapiFullVidFilter
 5280            EncodingJobInfo state,
 5281            EncodingOptions options,
 5282            string vidDecoder,
 5283            string vidEncoder)
 5284        {
 05285            var inW = state.VideoStream?.Width;
 05286            var inH = state.VideoStream?.Height;
 05287            var reqW = state.BaseRequest.Width;
 05288            var reqH = state.BaseRequest.Height;
 05289            var reqMaxW = state.BaseRequest.MaxWidth;
 05290            var reqMaxH = state.BaseRequest.MaxHeight;
 05291            var threeDFormat = state.MediaSource.Video3DFormat;
 5292
 05293            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05294            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05295            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05296            var isSwEncoder = !isVaapiEncoder;
 05297            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5298
 05299            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05300            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05301            var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
 05302            var doDeintH2645 = doDeintH264 || doDeintHevc;
 5303
 05304            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05305            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05306            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05307            var hasAssSubs = hasSubs
 05308                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05309                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5310
 05311            var rotation = state.VideoStream?.Rotation ?? 0;
 05312            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05313            var doVkTranspose = isVaapiDecoder && !string.IsNullOrEmpty(transposeDir);
 05314            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVkTranspose));
 05315            var swpInW = swapWAndH ? inH : inW;
 05316            var swpInH = swapWAndH ? inW : inH;
 5317
 5318            /* Make main filters for video stream */
 05319            var mainFilters = new List<string>();
 5320
 05321            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doVkTonemap));
 5322
 05323            if (isSwDecoder)
 5324            {
 5325                // INPUT sw surface(memory)
 5326                // sw deint
 05327                if (doDeintH2645)
 5328                {
 05329                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05330                    mainFilters.Add(swDeintFilter);
 5331                }
 5332
 05333                if (doVkTonemap || hasSubs)
 5334                {
 5335                    // sw => hw
 05336                    mainFilters.Add("hwupload=derive_device=vulkan");
 05337                    mainFilters.Add("format=vulkan");
 5338                }
 5339                else
 5340                {
 5341                    // sw scale
 05342                    var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW,
 05343                    mainFilters.Add(swScaleFilter);
 05344                    mainFilters.Add("format=nv12");
 5345                }
 5346            }
 05347            else if (isVaapiDecoder)
 5348            {
 5349                // INPUT vaapi surface(vram)
 05350                if (doVkTranspose || doVkTonemap || hasSubs)
 5351                {
 5352                    // map from vaapi to vulkan/drm via interop (Polaris/gfx8+).
 05353                    if (_mediaEncoder.EncoderVersion >= _minFFmpegAlteredVaVkInterop)
 5354                    {
 05355                        if (doVkTranspose || !_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5356                        {
 5357                            // disable the indirect va-drm-vk mapping since it's no longer reliable.
 05358                            mainFilters.Add("hwmap=derive_device=drm");
 05359                            mainFilters.Add("format=drm_prime");
 05360                            mainFilters.Add("hwmap=derive_device=vulkan");
 05361                            mainFilters.Add("format=vulkan");
 5362
 5363                            // workaround for libplacebo using the imported vulkan frame on gfx8.
 05364                            if (!_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5365                            {
 05366                                mainFilters.Add("scale_vulkan");
 5367                            }
 5368                        }
 05369                        else if (doVkTonemap || hasSubs)
 5370                        {
 5371                            // non ad-hoc libplacebo also accepts drm_prime direct input.
 05372                            mainFilters.Add("hwmap=derive_device=drm");
 05373                            mainFilters.Add("format=drm_prime");
 5374                        }
 5375                    }
 5376                    else // legacy va-vk mapping that works only in jellyfin-ffmpeg6
 5377                    {
 05378                        mainFilters.Add("hwmap=derive_device=vulkan");
 05379                        mainFilters.Add("format=vulkan");
 5380                    }
 5381                }
 5382                else
 5383                {
 5384                    // hw deint
 05385                    if (doDeintH2645)
 5386                    {
 05387                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05388                        mainFilters.Add(deintFilter);
 5389                    }
 5390
 5391                    // hw scale
 05392                    var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", "nv12", false, inW, inH, reqW, reqH, reqMaxW,
 5393
 05394                    if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder && !doVkTonemap)
 5395                    {
 05396                        hwScaleFilter += ":out_range=pc:mode=hq";
 5397                    }
 5398
 05399                    mainFilters.Add(hwScaleFilter);
 5400                }
 5401            }
 5402
 5403            // vk transpose
 05404            if (doVkTranspose)
 5405            {
 05406                if (string.Equals(transposeDir, "reversal", StringComparison.OrdinalIgnoreCase))
 5407                {
 05408                    mainFilters.Add("flip_vulkan");
 5409                }
 5410                else
 5411                {
 05412                    mainFilters.Add($"transpose_vulkan=dir={transposeDir}");
 5413                }
 5414            }
 5415
 5416            // vk libplacebo
 05417            if (doVkTonemap || hasSubs)
 5418            {
 05419                var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, req
 05420                mainFilters.Add(libplaceboFilter);
 05421                mainFilters.Add("format=vulkan");
 5422            }
 5423
 05424            if (doVkTonemap && !hasSubs)
 5425            {
 5426                // OUTPUT vaapi(nv12) surface(vram)
 5427                // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05428                mainFilters.Add("hwmap=derive_device=vaapi");
 05429                mainFilters.Add("format=vaapi");
 5430
 5431                // clear the surf->meta_offset and output nv12
 05432                mainFilters.Add("scale_vaapi=format=nv12");
 5433
 5434                // hw deint
 05435                if (doDeintH2645)
 5436                {
 05437                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05438                    mainFilters.Add(deintFilter);
 5439                }
 5440            }
 5441
 05442            if (!hasSubs)
 5443            {
 5444                // OUTPUT nv12 surface(memory)
 05445                if (isSwEncoder && (doVkTonemap || isVaapiDecoder))
 5446                {
 05447                    mainFilters.Add("hwdownload");
 05448                    mainFilters.Add("format=nv12");
 5449                }
 5450
 05451                if (isSwDecoder && isVaapiEncoder && !doVkTonemap)
 5452                {
 05453                    mainFilters.Add("hwupload_vaapi");
 5454                }
 5455            }
 5456
 5457            /* Make sub and overlay filters for subtitle stream */
 05458            var subFilters = new List<string>();
 05459            var overlayFilters = new List<string>();
 05460            if (hasSubs)
 5461            {
 05462                if (hasGraphicalSubs)
 5463                {
 05464                    var subW = state.SubtitleStream?.Width;
 05465                    var subH = state.SubtitleStream?.Height;
 05466                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05467                    subFilters.Add(subPreProcFilters);
 05468                    subFilters.Add("format=bgra");
 5469                }
 05470                else if (hasTextSubs)
 5471                {
 05472                    var framerate = state.VideoStream?.RealFrameRate;
 05473                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5474
 05475                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05476                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05477                    subFilters.Add(alphaSrcFilter);
 05478                    subFilters.Add("format=bgra");
 05479                    subFilters.Add(subTextSubtitlesFilter);
 5480                }
 5481
 05482                subFilters.Add("hwupload=derive_device=vulkan");
 05483                subFilters.Add("format=vulkan");
 5484
 05485                overlayFilters.Add("overlay_vulkan=eof_action=pass:repeatlast=0");
 5486
 05487                if (isSwEncoder)
 5488                {
 5489                    // OUTPUT nv12 surface(memory)
 05490                    overlayFilters.Add("scale_vulkan=format=nv12");
 05491                    overlayFilters.Add("hwdownload");
 05492                    overlayFilters.Add("format=nv12");
 5493                }
 05494                else if (isVaapiEncoder)
 5495                {
 5496                    // OUTPUT vaapi(nv12) surface(vram)
 5497                    // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05498                    overlayFilters.Add("hwmap=derive_device=vaapi");
 05499                    overlayFilters.Add("format=vaapi");
 5500
 5501                    // clear the surf->meta_offset and output nv12
 05502                    overlayFilters.Add("scale_vaapi=format=nv12");
 5503
 5504                    // hw deint
 05505                    if (doDeintH2645)
 5506                    {
 05507                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05508                        overlayFilters.Add(deintFilter);
 5509                    }
 5510                }
 5511            }
 5512
 05513            return (mainFilters, subFilters, overlayFilters);
 5514        }
 5515
 5516        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiLimitedVidFilter
 5517            EncodingJobInfo state,
 5518            EncodingOptions options,
 5519            string vidDecoder,
 5520            string vidEncoder)
 5521        {
 05522            var inW = state.VideoStream?.Width;
 05523            var inH = state.VideoStream?.Height;
 05524            var reqW = state.BaseRequest.Width;
 05525            var reqH = state.BaseRequest.Height;
 05526            var reqMaxW = state.BaseRequest.MaxWidth;
 05527            var reqMaxH = state.BaseRequest.MaxHeight;
 05528            var threeDFormat = state.MediaSource.Video3DFormat;
 5529
 05530            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05531            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05532            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05533            var isSwEncoder = !isVaapiEncoder;
 05534            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05535            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 05536            var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965;
 05537            var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd;
 5538
 05539            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05540            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05541            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05542            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5543
 05544            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05545            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05546            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 5547
 05548            var rotation = state.VideoStream?.Rotation ?? 0;
 05549            var swapWAndH = Math.Abs(rotation) == 90 && isSwDecoder;
 05550            var swpInW = swapWAndH ? inH : inW;
 05551            var swpInH = swapWAndH ? inW : inH;
 5552
 5553            /* Make main filters for video stream */
 05554            var mainFilters = new List<string>();
 5555
 05556            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5557
 05558            var outFormat = string.Empty;
 05559            if (isSwDecoder)
 5560            {
 5561                // INPUT sw surface(memory)
 5562                // sw deint
 05563                if (doDeintH2645)
 5564                {
 05565                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05566                    mainFilters.Add(swDeintFilter);
 5567                }
 5568
 05569                outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05570                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05571                if (isMjpegEncoder && !doOclTonemap)
 5572                {
 5573                    // sw decoder + hw mjpeg encoder
 05574                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5575                }
 5576
 5577                // sw scale
 05578                mainFilters.Add(swScaleFilter);
 05579                mainFilters.Add("format=" + outFormat);
 5580
 5581                // keep video at memory except ocl tonemap,
 5582                // since the overhead caused by hwupload >>> using sw filter.
 5583                // sw => hw
 05584                if (doOclTonemap)
 5585                {
 05586                    mainFilters.Add("hwupload=derive_device=opencl");
 5587                }
 5588            }
 05589            else if (isVaapiDecoder)
 5590            {
 5591                // INPUT vaapi surface(vram)
 5592                // hw deint
 05593                if (doDeintH2645)
 5594                {
 05595                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05596                    mainFilters.Add(deintFilter);
 5597                }
 5598
 05599                outFormat = doOclTonemap ? string.Empty : "nv12";
 05600                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, inW, inH, reqW, reqH, reqMaxW, 
 5601
 05602                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5603                {
 05604                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05605                    hwScaleFilter += ":mode=hq";
 5606                }
 5607
 5608                // allocate extra pool sizes for vaapi vpp
 05609                if (!string.IsNullOrEmpty(hwScaleFilter))
 5610                {
 05611                    hwScaleFilter += ":extra_hw_frames=24";
 5612                }
 5613
 5614                // hw scale
 05615                mainFilters.Add(hwScaleFilter);
 5616            }
 5617
 05618            if (doOclTonemap && isVaapiDecoder)
 5619            {
 05620                if (isi965Driver)
 5621                {
 5622                    // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05623                    mainFilters.Add("hwmap=derive_device=opencl");
 5624                }
 5625                else
 5626                {
 05627                    mainFilters.Add("hwdownload");
 05628                    mainFilters.Add("format=p010le");
 05629                    mainFilters.Add("hwupload=derive_device=opencl");
 5630                }
 5631            }
 5632
 5633            // ocl tonemap
 05634            if (doOclTonemap)
 5635            {
 05636                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05637                mainFilters.Add(tonemapFilter);
 5638            }
 5639
 05640            if (doOclTonemap && isVaInVaOut)
 5641            {
 05642                if (isi965Driver)
 5643                {
 5644                    // OUTPUT vaapi(nv12) surface(vram)
 5645                    // reverse-mapping via vaapi-opencl interop.
 05646                    mainFilters.Add("hwmap=derive_device=vaapi:reverse=1");
 05647                    mainFilters.Add("format=vaapi");
 5648                }
 5649            }
 5650
 05651            var memoryOutput = false;
 05652            var isUploadForOclTonemap = doOclTonemap && (isSwDecoder || (isVaapiDecoder && !isi965Driver));
 05653            var isHwmapNotUsable = hasGraphicalSubs || isUploadForOclTonemap;
 05654            var isHwmapForSubs = hasSubs && isVaapiDecoder;
 05655            var isHwUnmapForTextSubs = hasTextSubs && isVaInVaOut && !isUploadForOclTonemap;
 05656            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap || isHwmapForSubs)
 5657            {
 05658                memoryOutput = true;
 5659
 5660                // OUTPUT nv12 surface(memory)
 5661                // prefer hwmap to hwdownload on opencl/vaapi.
 05662                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap");
 05663                mainFilters.Add("format=nv12");
 5664            }
 5665
 5666            // OUTPUT nv12 surface(memory)
 05667            if (isSwDecoder && isVaapiEncoder)
 5668            {
 05669                memoryOutput = true;
 5670            }
 5671
 05672            if (memoryOutput)
 5673            {
 5674                // text subtitles
 05675                if (hasTextSubs)
 5676                {
 05677                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05678                    mainFilters.Add(textSubtitlesFilter);
 5679                }
 5680            }
 5681
 05682            if (isHwUnmapForTextSubs)
 5683            {
 05684                mainFilters.Add("hwmap");
 05685                mainFilters.Add("format=vaapi");
 5686            }
 05687            else if (memoryOutput && isVaapiEncoder)
 5688            {
 05689                if (!hasGraphicalSubs)
 5690                {
 05691                    mainFilters.Add("hwupload_vaapi");
 5692                }
 5693            }
 5694
 5695            /* Make sub and overlay filters for subtitle stream */
 05696            var subFilters = new List<string>();
 05697            var overlayFilters = new List<string>();
 05698            if (memoryOutput)
 5699            {
 05700                if (hasGraphicalSubs)
 5701                {
 05702                    var subW = state.SubtitleStream?.Width;
 05703                    var subH = state.SubtitleStream?.Height;
 05704                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05705                    subFilters.Add(subPreProcFilters);
 05706                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5707
 05708                    if (isVaapiEncoder)
 5709                    {
 05710                        overlayFilters.Add("hwupload_vaapi");
 5711                    }
 5712                }
 5713            }
 5714
 05715            return (mainFilters, subFilters, overlayFilters);
 5716        }
 5717
 5718        /// <summary>
 5719        /// Gets the parameter of Apple VideoToolBox filter chain.
 5720        /// </summary>
 5721        /// <param name="state">Encoding state.</param>
 5722        /// <param name="options">Encoding options.</param>
 5723        /// <param name="vidEncoder">Video encoder to use.</param>
 5724        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5725        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFilterChain(
 5726            EncodingJobInfo state,
 5727            EncodingOptions options,
 5728            string vidEncoder)
 5729        {
 05730            if (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 5731            {
 05732                return (null, null, null);
 5733            }
 5734
 5735            // ReSharper disable once InconsistentNaming
 05736            var isMacOS = OperatingSystem.IsMacOS();
 05737            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05738            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05739            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05740            var isVtFullSupported = isMacOS && IsVideoToolboxFullSupported();
 5741
 5742            // legacy videotoolbox pipeline (disable hw filters)
 05743            if (!(isVtEncoder || isVtDecoder)
 05744                || !isVtFullSupported
 05745                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5746            {
 05747                return GetSwVidFilterChain(state, options, vidEncoder);
 5748            }
 5749
 5750            // preferred videotoolbox + metal filters pipeline
 05751            return GetAppleVidFiltersPreferred(state, options, vidDecoder, vidEncoder);
 5752        }
 5753
 5754        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFiltersPrefer
 5755            EncodingJobInfo state,
 5756            EncodingOptions options,
 5757            string vidDecoder,
 5758            string vidEncoder)
 5759        {
 05760            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05761            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05762            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5763
 05764            var inW = state.VideoStream?.Width;
 05765            var inH = state.VideoStream?.Height;
 05766            var reqW = state.BaseRequest.Width;
 05767            var reqH = state.BaseRequest.Height;
 05768            var reqMaxW = state.BaseRequest.MaxWidth;
 05769            var reqMaxH = state.BaseRequest.MaxHeight;
 05770            var threeDFormat = state.MediaSource.Video3DFormat;
 5771
 05772            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05773            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05774            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05775            var doVtTonemap = IsVideoToolboxTonemapAvailable(state, options);
 05776            var doMetalTonemap = !doVtTonemap && IsHwTonemapAvailable(state, options);
 05777            var usingHwSurface = isVtDecoder && (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface);
 5778
 05779            var rotation = state.VideoStream?.Rotation ?? 0;
 05780            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05781            var doVtTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_vt");
 05782            var swapWAndH = Math.Abs(rotation) == 90 && doVtTranspose;
 05783            var swpInW = swapWAndH ? inH : inW;
 05784            var swpInH = swapWAndH ? inW : inH;
 5785
 05786            var scaleFormat = string.Empty;
 5787            // Use P010 for Metal tone mapping, otherwise force an 8bit output.
 05788            if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase))
 5789            {
 05790                if (doMetalTonemap)
 5791                {
 05792                    if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 5793                    {
 05794                        scaleFormat = "p010le";
 5795                    }
 5796                }
 5797                else
 5798                {
 05799                    scaleFormat = "nv12";
 5800                }
 5801            }
 5802
 05803            var hwScaleFilter = GetHwScaleFilter("scale", "vt", scaleFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW,
 5804
 05805            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05806            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05807            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05808            var hasAssSubs = hasSubs
 05809                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05810                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5811
 5812            /* Make main filters for video stream */
 05813            var mainFilters = new List<string>();
 5814
 5815            // hw deint
 05816            if (doDeintH2645)
 5817            {
 05818                var deintFilter = GetHwDeinterlaceFilter(state, options, "videotoolbox");
 05819                mainFilters.Add(deintFilter);
 5820            }
 5821
 5822            // hw transpose
 05823            if (doVtTranspose)
 5824            {
 05825                mainFilters.Add($"transpose_vt=dir={transposeDir}");
 5826            }
 5827
 05828            if (doVtTonemap)
 5829            {
 5830                const string VtTonemapArgs = "color_matrix=bt709:color_primaries=bt709:color_transfer=bt709";
 5831
 5832                // scale_vt can handle scaling & tonemapping in one shot, just like vpp_qsv.
 05833                hwScaleFilter = string.IsNullOrEmpty(hwScaleFilter)
 05834                    ? "scale_vt=" + VtTonemapArgs
 05835                    : hwScaleFilter + ":" + VtTonemapArgs;
 5836            }
 5837
 5838            // hw scale & vt tonemap
 05839            mainFilters.Add(hwScaleFilter);
 5840
 5841            // Metal tonemap
 05842            if (doMetalTonemap)
 5843            {
 05844                var tonemapFilter = GetHwTonemapFilter(options, "videotoolbox", "nv12", isMjpegEncoder);
 05845                mainFilters.Add(tonemapFilter);
 5846            }
 5847
 5848            /* Make sub and overlay filters for subtitle stream */
 05849            var subFilters = new List<string>();
 05850            var overlayFilters = new List<string>();
 5851
 05852            if (hasSubs)
 5853            {
 05854                if (hasGraphicalSubs)
 5855                {
 05856                    var subW = state.SubtitleStream?.Width;
 05857                    var subH = state.SubtitleStream?.Height;
 05858                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05859                    subFilters.Add(subPreProcFilters);
 05860                    subFilters.Add("format=bgra");
 5861                }
 05862                else if (hasTextSubs)
 5863                {
 05864                    var framerate = state.VideoStream?.RealFrameRate;
 05865                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5866
 05867                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05868                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05869                    subFilters.Add(alphaSrcFilter);
 05870                    subFilters.Add("format=bgra");
 05871                    subFilters.Add(subTextSubtitlesFilter);
 5872                }
 5873
 05874                subFilters.Add("hwupload");
 05875                overlayFilters.Add("overlay_videotoolbox=eof_action=pass:repeatlast=0");
 5876            }
 5877
 05878            if (usingHwSurface)
 5879            {
 05880                if (!isVtEncoder)
 5881                {
 05882                    mainFilters.Add("hwdownload");
 05883                    mainFilters.Add("format=nv12");
 5884                }
 5885
 05886                return (mainFilters, subFilters, overlayFilters);
 5887            }
 5888
 5889            // For old jellyfin-ffmpeg that has broken hwsurface, add a hwupload
 05890            var needFiltering = mainFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05891                                subFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05892                                overlayFilters.Any(f => !string.IsNullOrEmpty(f));
 05893            if (needFiltering)
 5894            {
 5895                // INPUT videotoolbox/memory surface(vram/uma)
 5896                // this will pass-through automatically if in/out format matches.
 05897                mainFilters.Insert(0, "hwupload");
 05898                mainFilters.Insert(0, "format=nv12|p010le|videotoolbox_vld");
 5899
 05900                if (!isVtEncoder)
 5901                {
 05902                    mainFilters.Add("hwdownload");
 05903                    mainFilters.Add("format=nv12");
 5904                }
 5905            }
 5906
 05907            return (mainFilters, subFilters, overlayFilters);
 5908        }
 5909
 5910        /// <summary>
 5911        /// Gets the parameter of Rockchip RKMPP/RKRGA filter chain.
 5912        /// </summary>
 5913        /// <param name="state">Encoding state.</param>
 5914        /// <param name="options">Encoding options.</param>
 5915        /// <param name="vidEncoder">Video encoder to use.</param>
 5916        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5917        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFilterChain(
 5918            EncodingJobInfo state,
 5919            EncodingOptions options,
 5920            string vidEncoder)
 5921        {
 05922            if (options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 5923            {
 05924                return (null, null, null);
 5925            }
 5926
 05927            var isLinux = OperatingSystem.IsLinux();
 05928            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05929            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05930            var isSwEncoder = !vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05931            var isRkmppOclSupported = isLinux && IsRkmppFullSupported() && IsOpenclFullSupported();
 5932
 05933            if ((isSwDecoder && isSwEncoder)
 05934                || !isRkmppOclSupported
 05935                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5936            {
 05937                return GetSwVidFilterChain(state, options, vidEncoder);
 5938            }
 5939
 5940            // preferred rkmpp + rkrga + opencl filters pipeline
 05941            if (isRkmppOclSupported)
 5942            {
 05943                return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5944            }
 5945
 05946            return (null, null, null);
 5947        }
 5948
 5949        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFiltersPrefer
 5950            EncodingJobInfo state,
 5951            EncodingOptions options,
 5952            string vidDecoder,
 5953            string vidEncoder)
 5954        {
 05955            var inW = state.VideoStream?.Width;
 05956            var inH = state.VideoStream?.Height;
 05957            var reqW = state.BaseRequest.Width;
 05958            var reqH = state.BaseRequest.Height;
 05959            var reqMaxW = state.BaseRequest.MaxWidth;
 05960            var reqMaxH = state.BaseRequest.MaxHeight;
 05961            var threeDFormat = state.MediaSource.Video3DFormat;
 5962
 05963            var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05964            var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05965            var isSwDecoder = !isRkmppDecoder;
 05966            var isSwEncoder = !isRkmppEncoder;
 05967            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05968            var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder;
 05969            var isEncoderSupportAfbc = isRkmppEncoder
 05970                && (vidEncoder.Contains("h264", StringComparison.OrdinalIgnoreCase)
 05971                    || vidEncoder.Contains("hevc", StringComparison.OrdinalIgnoreCase));
 5972
 05973            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05974            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05975            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05976            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5977
 05978            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05979            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05980            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05981            var hasAssSubs = hasSubs
 05982                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05983                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 05984            var subW = state.SubtitleStream?.Width;
 05985            var subH = state.SubtitleStream?.Height;
 5986
 05987            var rotation = state.VideoStream?.Rotation ?? 0;
 05988            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05989            var doRkVppTranspose = !string.IsNullOrEmpty(transposeDir);
 05990            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isRkmppDecoder && doRkVppTranspose));
 05991            var swpInW = swapWAndH ? inH : inW;
 05992            var swpInH = swapWAndH ? inW : inH;
 5993
 5994            /* Make main filters for video stream */
 05995            var mainFilters = new List<string>();
 5996
 05997            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5998
 05999            if (isSwDecoder)
 6000            {
 6001                // INPUT sw surface(memory)
 6002                // sw deint
 06003                if (doDeintH2645)
 6004                {
 06005                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 06006                    mainFilters.Add(swDeintFilter);
 6007                }
 6008
 06009                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 06010                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 06011                if (isMjpegEncoder && !doOclTonemap)
 6012                {
 6013                    // sw decoder + hw mjpeg encoder
 06014                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 6015                }
 6016
 06017                if (!string.IsNullOrEmpty(swScaleFilter))
 6018                {
 06019                    swScaleFilter += ":flags=fast_bilinear";
 6020                }
 6021
 6022                // sw scale
 06023                mainFilters.Add(swScaleFilter);
 06024                mainFilters.Add($"format={outFormat}");
 6025
 6026                // keep video at memory except ocl tonemap,
 6027                // since the overhead caused by hwupload >>> using sw filter.
 6028                // sw => hw
 06029                if (doOclTonemap)
 6030                {
 06031                    mainFilters.Add("hwupload=derive_device=opencl");
 6032                }
 6033            }
 06034            else if (isRkmppDecoder)
 6035            {
 6036                // INPUT rkmpp/drm surface(gem/dma-heap)
 6037
 06038                var isFullAfbcPipeline = isEncoderSupportAfbc && isDrmInDrmOut && !doOclTonemap;
 06039                var swapOutputWandH = doRkVppTranspose && swapWAndH;
 06040                var outFormat = doOclTonemap ? "p010" : "nv12";
 06041                var hwScaleFilter = GetHwScaleFilter("vpp", "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, r
 06042                var doScaling = !string.IsNullOrEmpty(GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, sw
 6043
 06044                if (!hasSubs
 06045                     || doRkVppTranspose
 06046                     || !isFullAfbcPipeline
 06047                     || doScaling)
 6048                {
 06049                    var isScaleRatioSupported = IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 6050
 6051                    // RGA3 hardware only support (1/8 ~ 8) scaling in each blit operation,
 6052                    // but in Trickplay there's a case: (3840/320 == 12), enable 2pass for it
 06053                    if (doScaling && !isScaleRatioSupported)
 6054                    {
 6055                        // Vendor provided BSP kernel has an RGA driver bug that causes the output to be corrupted for P
 6056                        // Use NV15 instead of P010 to avoid the issue.
 6057                        // SDR inputs are using BGRA formats already which is not affected.
 06058                        var intermediateFormat = doOclTonemap ? "nv15" : (isMjpegEncoder ? "bgra" : outFormat);
 06059                        var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={intermediateFormat}:force_o
 06060                        mainFilters.Add(hwScaleFilterFirstPass);
 6061                    }
 6062
 6063                    // The RKMPP MJPEG encoder on some newer chip models no longer supports RGB input.
 6064                    // Use 2pass here to enable RGA output of full-range YUV in the 2nd pass.
 06065                    if (isMjpegEncoder && !doOclTonemap && ((doScaling && isScaleRatioSupported) || !doScaling))
 6066                    {
 06067                        var hwScaleFilterFirstPass = "vpp_rkrga=format=bgra:afbc=1";
 06068                        mainFilters.Add(hwScaleFilterFirstPass);
 6069                    }
 6070
 06071                    if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose)
 6072                    {
 06073                        hwScaleFilter += $":transpose={transposeDir}";
 6074                    }
 6075
 6076                    // try enabling AFBC to save DDR bandwidth
 06077                    if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline)
 6078                    {
 06079                        hwScaleFilter += ":afbc=1";
 6080                    }
 6081
 6082                    // hw transpose & scale
 06083                    mainFilters.Add(hwScaleFilter);
 6084                }
 6085            }
 6086
 06087            if (doOclTonemap && isRkmppDecoder)
 6088            {
 6089                // map from rkmpp/drm to opencl via drm-opencl interop.
 06090                mainFilters.Add("hwmap=derive_device=opencl");
 6091            }
 6092
 6093            // ocl tonemap
 06094            if (doOclTonemap)
 6095            {
 06096                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 06097                mainFilters.Add(tonemapFilter);
 6098            }
 6099
 06100            var memoryOutput = false;
 06101            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 06102            if ((isRkmppDecoder && isSwEncoder) || isUploadForOclTonemap)
 6103            {
 06104                memoryOutput = true;
 6105
 6106                // OUTPUT nv12 surface(memory)
 06107                mainFilters.Add("hwdownload");
 06108                mainFilters.Add("format=nv12");
 6109            }
 6110
 6111            // OUTPUT nv12 surface(memory)
 06112            if (isSwDecoder && isRkmppEncoder)
 6113            {
 06114                memoryOutput = true;
 6115            }
 6116
 06117            if (memoryOutput)
 6118            {
 6119                // text subtitles
 06120                if (hasTextSubs)
 6121                {
 06122                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 06123                    mainFilters.Add(textSubtitlesFilter);
 6124                }
 6125            }
 6126
 06127            if (isDrmInDrmOut)
 6128            {
 06129                if (doOclTonemap)
 6130                {
 6131                    // OUTPUT drm(nv12) surface(gem/dma-heap)
 6132                    // reverse-mapping via drm-opencl interop.
 06133                    mainFilters.Add("hwmap=derive_device=rkmpp:reverse=1");
 06134                    mainFilters.Add("format=drm_prime");
 6135                }
 6136            }
 6137
 6138            /* Make sub and overlay filters for subtitle stream */
 06139            var subFilters = new List<string>();
 06140            var overlayFilters = new List<string>();
 06141            if (isDrmInDrmOut)
 6142            {
 06143                if (hasSubs)
 6144                {
 06145                    var subMaxH = 1080;
 06146                    if (hasGraphicalSubs)
 6147                    {
 06148                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 06149                        subFilters.Add(subPreProcFilters);
 06150                        subFilters.Add("format=bgra");
 6151                    }
 06152                    else if (hasTextSubs)
 6153                    {
 06154                        var framerate = state.VideoStream?.RealFrameRate;
 06155                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 6156
 6157                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 06158                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, subMaxH, subF
 06159                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 06160                        subFilters.Add(alphaSrcFilter);
 06161                        subFilters.Add("format=bgra");
 06162                        subFilters.Add(subTextSubtitlesFilter);
 6163                    }
 6164
 06165                    subFilters.Add("hwupload=derive_device=rkmpp");
 6166
 6167                    // offload 1080p+ subtitles swscale upscaling from CPU to RGA
 06168                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 06169                    if (overlayW.HasValue && overlayH.HasValue && overlayH.Value > subMaxH)
 6170                    {
 06171                        subFilters.Add($"vpp_rkrga=w={overlayW.Value}:h={overlayH.Value}:format=bgra:afbc=1");
 6172                    }
 6173
 6174                    // try enabling AFBC to save DDR bandwidth
 06175                    var hwOverlayFilter = "overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12";
 06176                    if (isEncoderSupportAfbc)
 6177                    {
 06178                        hwOverlayFilter += ":afbc=1";
 6179                    }
 6180
 06181                    overlayFilters.Add(hwOverlayFilter);
 6182                }
 6183            }
 06184            else if (memoryOutput)
 6185            {
 06186                if (hasGraphicalSubs)
 6187                {
 06188                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 06189                    subFilters.Add(subPreProcFilters);
 06190                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 6191                }
 6192            }
 6193
 06194            return (mainFilters, subFilters, overlayFilters);
 6195        }
 6196
 6197        /// <summary>
 6198        /// Gets the parameter of video processing filters.
 6199        /// </summary>
 6200        /// <param name="state">Encoding state.</param>
 6201        /// <param name="options">Encoding options.</param>
 6202        /// <param name="outputVideoCodec">Video codec to use.</param>
 6203        /// <returns>The video processing filters parameter.</returns>
 6204        public string GetVideoProcessingFilterParam(
 6205            EncodingJobInfo state,
 6206            EncodingOptions options,
 6207            string outputVideoCodec)
 6208        {
 06209            var videoStream = state.VideoStream;
 06210            if (videoStream is null)
 6211            {
 06212                return string.Empty;
 6213            }
 6214
 06215            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 06216            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 06217            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 6218
 6219            List<string> mainFilters;
 6220            List<string> subFilters;
 6221            List<string> overlayFilters;
 6222
 06223            (mainFilters, subFilters, overlayFilters) = options.HardwareAccelerationType switch
 06224            {
 06225                HardwareAccelerationType.vaapi => GetVaapiVidFilterChain(state, options, outputVideoCodec),
 06226                HardwareAccelerationType.amf => GetAmdVidFilterChain(state, options, outputVideoCodec),
 06227                HardwareAccelerationType.qsv => GetIntelVidFilterChain(state, options, outputVideoCodec),
 06228                HardwareAccelerationType.nvenc => GetNvidiaVidFilterChain(state, options, outputVideoCodec),
 06229                HardwareAccelerationType.videotoolbox => GetAppleVidFilterChain(state, options, outputVideoCodec),
 06230                HardwareAccelerationType.rkmpp => GetRkmppVidFilterChain(state, options, outputVideoCodec),
 06231                _ => GetSwVidFilterChain(state, options, outputVideoCodec),
 06232            };
 6233
 06234            mainFilters?.RemoveAll(string.IsNullOrEmpty);
 06235            subFilters?.RemoveAll(string.IsNullOrEmpty);
 06236            overlayFilters?.RemoveAll(string.IsNullOrEmpty);
 6237
 06238            var framerate = GetFramerateParam(state);
 06239            if (framerate.HasValue)
 6240            {
 06241                mainFilters.Insert(0, string.Format(
 06242                    CultureInfo.InvariantCulture,
 06243                    "fps={0}",
 06244                    framerate.Value));
 6245            }
 6246
 06247            var mainStr = string.Empty;
 06248            if (mainFilters?.Count > 0)
 6249            {
 06250                mainStr = string.Format(
 06251                    CultureInfo.InvariantCulture,
 06252                    "{0}",
 06253                    string.Join(',', mainFilters));
 6254            }
 6255
 06256            if (overlayFilters?.Count == 0)
 6257            {
 6258                // -vf "scale..."
 06259                return string.IsNullOrEmpty(mainStr) ? string.Empty : " -vf \"" + mainStr + "\"";
 6260            }
 6261
 06262            if (overlayFilters?.Count > 0
 06263                && subFilters?.Count > 0
 06264                && state.SubtitleStream is not null)
 6265            {
 6266                // overlay graphical/text subtitles
 06267                var subStr = string.Format(
 06268                        CultureInfo.InvariantCulture,
 06269                        "{0}",
 06270                        string.Join(',', subFilters));
 6271
 06272                var overlayStr = string.Format(
 06273                        CultureInfo.InvariantCulture,
 06274                        "{0}",
 06275                        string.Join(',', overlayFilters));
 6276
 06277                var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
 06278                var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 06279                var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 6280
 06281                if (hasSubs)
 6282                {
 6283                    // -filter_complex "[0:s]scale=s[sub]..."
 06284                    var filterStr = string.IsNullOrEmpty(mainStr)
 06285                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]{5}\""
 06286                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6287
 06288                    if (hasTextSubs)
 6289                    {
 06290                        filterStr = string.IsNullOrEmpty(mainStr)
 06291                            ? " -filter_complex \"{4}[sub];[0:{2}][sub]{5}\""
 06292                            : " -filter_complex \"{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6293                    }
 6294
 06295                    return string.Format(
 06296                        CultureInfo.InvariantCulture,
 06297                        filterStr,
 06298                        mapPrefix,
 06299                        subtitleStreamIndex,
 06300                        videoStreamIndex,
 06301                        mainStr,
 06302                        subStr,
 06303                        overlayStr);
 6304                }
 6305            }
 6306
 06307            return string.Empty;
 6308        }
 6309
 6310        public string GetOverwriteColorPropertiesParam(EncodingJobInfo state, bool isTonemapAvailable)
 6311        {
 06312            if (isTonemapAvailable)
 6313            {
 06314                return GetInputHdrParam(state.VideoStream?.ColorTransfer);
 6315            }
 6316
 06317            return GetOutputSdrParam(null);
 6318        }
 6319
 6320        public string GetInputHdrParam(string colorTransfer)
 6321        {
 06322            if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
 6323            {
 6324                // HLG
 06325                return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
 6326            }
 6327
 6328            // HDR10
 06329            return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc";
 6330        }
 6331
 6332        public string GetOutputSdrParam(string tonemappingRange)
 6333        {
 6334            // SDR
 06335            if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
 6336            {
 06337                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
 6338            }
 6339
 06340            if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
 6341            {
 06342                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc";
 6343            }
 6344
 06345            return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
 6346        }
 6347
 6348        public static int GetVideoColorBitDepth(EncodingJobInfo state)
 6349        {
 06350            var videoStream = state.VideoStream;
 06351            if (videoStream is not null)
 6352            {
 06353                if (videoStream.BitDepth.HasValue)
 6354                {
 06355                    return videoStream.BitDepth.Value;
 6356                }
 6357
 06358                if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
 06359                    || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
 06360                    || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 06361                    || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
 6362                {
 06363                    return 8;
 6364                }
 6365
 06366                if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 06367                    || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 06368                    || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
 6369                {
 06370                    return 10;
 6371                }
 6372
 06373                if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 06374                    || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 06375                    || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
 6376                {
 06377                    return 12;
 6378                }
 6379
 06380                return 8;
 6381            }
 6382
 06383            return 0;
 6384        }
 6385
 6386        /// <summary>
 6387        /// Gets the ffmpeg option string for the hardware accelerated video decoder.
 6388        /// </summary>
 6389        /// <param name="state">The encoding job info.</param>
 6390        /// <param name="options">The encoding options.</param>
 6391        /// <returns>The option string or null if none available.</returns>
 6392        protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
 6393        {
 46394            var videoStream = state.VideoStream;
 46395            var mediaSource = state.MediaSource;
 46396            if (videoStream is null || mediaSource is null)
 6397            {
 06398                return null;
 6399            }
 6400
 6401            // HWA decoders can handle both video files and video folders.
 46402            var videoType = state.VideoType;
 46403            if (videoType != VideoType.VideoFile
 46404                && videoType != VideoType.Iso
 46405                && videoType != VideoType.Dvd
 46406                && videoType != VideoType.BluRay)
 6407            {
 06408                return null;
 6409            }
 6410
 46411            if (IsCopyCodec(state.OutputVideoCodec))
 6412            {
 06413                return null;
 6414            }
 6415
 46416            var hardwareAccelerationType = options.HardwareAccelerationType;
 6417
 46418            if (!string.IsNullOrEmpty(videoStream.Codec) && hardwareAccelerationType != HardwareAccelerationType.none)
 6419            {
 06420                var bitDepth = GetVideoColorBitDepth(state);
 6421
 6422                // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support for most platforms
 06423                if (bitDepth == 10
 06424                    && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06425                         || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
 06426                         || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)
 06427                         || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)))
 6428                {
 6429                    // RKMPP has H.264 Hi10P decoder
 06430                    bool hasHardwareHi10P = hardwareAccelerationType == HardwareAccelerationType.rkmpp;
 6431
 6432                    // VideoToolbox on Apple Silicon has H.264 Hi10P mode enabled after macOS 14.6
 06433                    if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox)
 6434                    {
 06435                        var ver = Environment.OSVersion.Version;
 06436                        var arch = RuntimeInformation.OSArchitecture;
 06437                        if (arch.Equals(Architecture.Arm64) && ver >= new Version(14, 6))
 6438                        {
 06439                            hasHardwareHi10P = true;
 6440                        }
 6441                    }
 6442
 06443                    if (!hasHardwareHi10P
 06444                        && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6445                    {
 06446                        return null;
 6447                    }
 6448                }
 6449
 6450                // Block unsupported H.264 Hi422P and Hi444PP profiles, which can be encoded with 4:2:0 pixel format
 06451                if (string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)
 06452                    && ((videoStream.Profile?.Contains("4:2:2", StringComparison.OrdinalIgnoreCase) ?? false)
 06453                        || (videoStream.Profile?.Contains("4:4:4", StringComparison.OrdinalIgnoreCase) ?? false)))
 6454                {
 6455                    // VideoToolbox on Apple Silicon has H.264 Hi444PP and theoretically also has Hi422P
 06456                    if (!(hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06457                          && RuntimeInformation.OSArchitecture.Equals(Architecture.Arm64)))
 6458                    {
 06459                        return null;
 6460                    }
 6461                }
 6462
 06463                var decoder = hardwareAccelerationType switch
 06464                {
 06465                    HardwareAccelerationType.vaapi => GetVaapiVidDecoder(state, options, videoStream, bitDepth),
 06466                    HardwareAccelerationType.amf => GetAmfVidDecoder(state, options, videoStream, bitDepth),
 06467                    HardwareAccelerationType.qsv => GetQsvHwVidDecoder(state, options, videoStream, bitDepth),
 06468                    HardwareAccelerationType.nvenc => GetNvdecVidDecoder(state, options, videoStream, bitDepth),
 06469                    HardwareAccelerationType.videotoolbox => GetVideotoolboxVidDecoder(state, options, videoStream, bitD
 06470                    HardwareAccelerationType.rkmpp => GetRkmppVidDecoder(state, options, videoStream, bitDepth),
 06471                    _ => string.Empty
 06472                };
 6473
 06474                if (!string.IsNullOrEmpty(decoder))
 6475                {
 06476                    return decoder;
 6477                }
 6478            }
 6479
 6480            // leave blank so ffmpeg will decide
 46481            return null;
 6482        }
 6483
 6484        /// <summary>
 6485        /// Gets a hw decoder name.
 6486        /// </summary>
 6487        /// <param name="options">Encoding options.</param>
 6488        /// <param name="decoderPrefix">Decoder prefix.</param>
 6489        /// <param name="decoderSuffix">Decoder suffix.</param>
 6490        /// <param name="videoCodec">Video codec to use.</param>
 6491        /// <param name="bitDepth">Video color bit depth.</param>
 6492        /// <returns>Hardware decoder name.</returns>
 6493        public string GetHwDecoderName(EncodingOptions options, string decoderPrefix, string decoderSuffix, string video
 6494        {
 06495            if (string.IsNullOrEmpty(decoderPrefix) || string.IsNullOrEmpty(decoderSuffix))
 6496            {
 06497                return null;
 6498            }
 6499
 06500            var decoderName = decoderPrefix + '_' + decoderSuffix;
 6501
 06502            var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains
 6503
 6504            // VideoToolbox decoders have built-in SW fallback
 06505            if (bitDepth == 10
 06506                && isCodecAvailable
 06507                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6508            {
 06509                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06510                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
 06511                    && !options.EnableDecodingColorDepth10Hevc)
 6512                {
 06513                    return null;
 6514                }
 6515
 06516                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06517                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06518                    && !options.EnableDecodingColorDepth10Vp9)
 6519                {
 06520                    return null;
 6521                }
 6522            }
 6523
 06524            if (string.Equals(decoderSuffix, "cuvid", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdec
 6525            {
 06526                return null;
 6527            }
 6528
 06529            if (string.Equals(decoderSuffix, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwD
 6530            {
 06531                return null;
 6532            }
 6533
 06534            if (string.Equals(decoderSuffix, "rkmpp", StringComparison.OrdinalIgnoreCase))
 6535            {
 06536                return null;
 6537            }
 6538
 06539            return isCodecAvailable ? (" -c:v " + decoderName) : null;
 6540        }
 6541
 6542        /// <summary>
 6543        /// Gets a hwaccel type to use as a hardware decoder depending on the system.
 6544        /// </summary>
 6545        /// <param name="state">Encoding state.</param>
 6546        /// <param name="options">Encoding options.</param>
 6547        /// <param name="videoCodec">Video codec to use.</param>
 6548        /// <param name="bitDepth">Video color bit depth.</param>
 6549        /// <param name="outputHwSurface">Specifies if output hw surface.</param>
 6550        /// <returns>Hardware accelerator type.</returns>
 6551        public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, int bitDepth, bo
 6552        {
 06553            var isWindows = OperatingSystem.IsWindows();
 06554            var isLinux = OperatingSystem.IsLinux();
 06555            var isMacOS = OperatingSystem.IsMacOS();
 06556            var isD3d11Supported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va");
 06557            var isVaapiSupported = isLinux && IsVaapiSupported(state);
 06558            var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported();
 06559            var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv");
 06560            var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
 06561            var isRkmppSupported = isLinux && IsRkmppFullSupported();
 06562            var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCas
 06563            var hardwareAccelerationType = options.HardwareAccelerationType;
 6564
 06565            var ffmpegVersion = _mediaEncoder.EncoderVersion;
 6566
 6567            // Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
 06568            var isAv1 = ffmpegVersion < _minFFmpegImplicitHwaccel
 06569                && string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
 6570
 6571            // Allow profile mismatch if decoding H.264 baseline with d3d11va and vaapi hwaccels.
 06572            var profileMismatch = string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
 06573                && string.Equals(state.VideoStream?.Profile, "baseline", StringComparison.OrdinalIgnoreCase);
 6574
 6575            // Disable the extra internal copy in nvdec. We already handle it in filter chain.
 06576            var nvdecNoInternalCopy = ffmpegVersion >= _minFFmpegHwaUnsafeOutput;
 6577
 6578            // Strip the display rotation side data from the transposed fmp4 output stream.
 06579            var stripRotationData = (state.VideoStream?.Rotation ?? 0) != 0
 06580                && ffmpegVersion >= _minFFmpegDisplayRotationOption;
 06581            var stripRotationDataArgs = stripRotationData ? " -display_rotation 0" : string.Empty;
 6582
 6583            // VideoToolbox decoders have built-in SW fallback
 06584            if (isCodecAvailable
 06585                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6586            {
 06587                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06588                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase))
 6589                {
 06590                    if (IsVideoStreamHevcRext(state))
 6591                    {
 06592                        if (bitDepth <= 10 && !options.EnableDecodingColorDepth10HevcRext)
 6593                        {
 06594                            return null;
 6595                        }
 6596
 06597                        if (bitDepth == 12 && !options.EnableDecodingColorDepth12HevcRext)
 6598                        {
 06599                            return null;
 6600                        }
 6601
 06602                        if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06603                            && !_mediaEncoder.IsVaapiDeviceInteliHD)
 6604                        {
 06605                            return null;
 6606                        }
 6607                    }
 06608                    else if (bitDepth == 10 && !options.EnableDecodingColorDepth10Hevc)
 6609                    {
 06610                        return null;
 6611                    }
 6612                }
 6613
 06614                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06615                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06616                    && bitDepth == 10
 06617                    && !options.EnableDecodingColorDepth10Vp9)
 6618                {
 06619                    return null;
 6620                }
 6621            }
 6622
 6623            // Intel qsv/d3d11va/vaapi
 06624            if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 6625            {
 06626                if (options.PreferSystemNativeHwDecoder)
 6627                {
 06628                    if (isVaapiSupported && isCodecAvailable)
 6629                    {
 06630                        return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + st
 06631                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " 
 6632                    }
 6633
 06634                    if (isD3d11Supported && isCodecAvailable)
 6635                    {
 06636                        return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + 
 06637                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 
 6638                    }
 6639                }
 6640                else
 6641                {
 06642                    if (isQsvSupported && isCodecAvailable)
 6643                    {
 06644                        return " -hwaccel qsv" + (outputHwSurface ? " -hwaccel_output_format qsv -noautorotate" + stripR
 6645                    }
 6646                }
 6647            }
 6648
 6649            // Nvidia cuda
 06650            if (hardwareAccelerationType == HardwareAccelerationType.nvenc)
 6651            {
 06652                if (isCudaSupported && isCodecAvailable)
 6653                {
 06654                    if (options.EnableEnhancedNvdecDecoder)
 6655                    {
 6656                        // set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support.
 06657                        return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stri
 06658                            + (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + 
 6659                    }
 6660
 6661                    // cuvid decoder doesn't have threading issue.
 06662                    return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stripRot
 6663                }
 6664            }
 6665
 6666            // Amd d3d11va
 06667            if (hardwareAccelerationType == HardwareAccelerationType.amf)
 6668            {
 06669                if (isD3d11Supported && isCodecAvailable)
 6670                {
 06671                    return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + stri
 06672                        + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 2" +
 6673                }
 6674            }
 6675
 6676            // Vaapi
 06677            if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06678                && isVaapiSupported
 06679                && isCodecAvailable)
 6680            {
 06681                return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + stripRotat
 06682                    + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1
 6683            }
 6684
 6685            // Apple videotoolbox
 06686            if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06687                && isVideotoolboxSupported
 06688                && isCodecAvailable)
 6689            {
 06690                return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string
 6691            }
 6692
 6693            // Rockchip rkmpp
 06694            if (hardwareAccelerationType == HardwareAccelerationType.rkmpp
 06695                && isRkmppSupported
 06696                && isCodecAvailable)
 6697            {
 06698                return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime -noautorotate" + stripR
 6699            }
 6700
 06701            return null;
 6702        }
 6703
 6704        public string GetQsvHwVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6705        {
 06706            var isWindows = OperatingSystem.IsWindows();
 06707            var isLinux = OperatingSystem.IsLinux();
 6708
 06709            if ((!isWindows && !isLinux)
 06710                || options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 6711            {
 06712                return null;
 6713            }
 6714
 06715            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 06716            var isIntelDx11OclSupported = isWindows
 06717                && _mediaEncoder.SupportsHwaccel("d3d11va")
 06718                && isQsvOclSupported;
 06719            var isIntelVaapiOclSupported = isLinux
 06720                && IsVaapiSupported(state)
 06721                && isQsvOclSupported;
 06722            var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
 06723                && _mediaEncoder.SupportsFilter("alphasrc");
 6724
 06725            var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06726                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06727            var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 06728            var is8_10_12bitSwFormatsQsv = is8_10bitSwFormatsQsv
 06729                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06730                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06731                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06732                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06733                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06734                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06735                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6736            // TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
 6737
 06738            if (is8bitSwFormatsQsv)
 6739            {
 06740                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06741                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6742                {
 06743                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6744                }
 6745
 06746                if (string.Equals(videoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase))
 6747                {
 06748                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6749                }
 6750
 06751                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6752                {
 06753                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6754                }
 6755
 06756                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6757                {
 06758                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6759                }
 6760            }
 6761
 06762            if (is8_10bitSwFormatsQsv)
 6763            {
 06764                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6765                {
 06766                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6767                }
 6768
 06769                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 6770                {
 06771                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6772                }
 6773            }
 6774
 06775            if (is8_10_12bitSwFormatsQsv)
 6776            {
 06777                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06778                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6779                {
 06780                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6781                }
 6782            }
 6783
 06784            return null;
 6785        }
 6786
 6787        public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6788        {
 06789            if ((!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
 06790                || options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 6791            {
 06792                return null;
 6793            }
 6794
 06795            var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
 06796            var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06797                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06798            var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06799            var is8_10_12bitSwFormatsNvdec = is8_10bitSwFormatsNvdec
 06800                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06801                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06802                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06803                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6804            // TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
 6805
 06806            if (is8bitSwFormatsNvdec)
 6807            {
 06808                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06809                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6810                {
 06811                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6812                }
 6813
 06814                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6815                {
 06816                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6817                }
 6818
 06819                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6820                {
 06821                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6822                }
 6823
 06824                if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6825                {
 06826                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpe
 6827                }
 6828
 06829                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6830                {
 06831                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6832                }
 6833            }
 6834
 06835            if (is8_10bitSwFormatsNvdec)
 6836            {
 06837                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6838                {
 06839                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6840                }
 6841
 06842                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6843                {
 06844                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6845                }
 6846            }
 6847
 06848            if (is8_10_12bitSwFormatsNvdec)
 6849            {
 06850                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06851                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6852                {
 06853                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6854                }
 6855            }
 6856
 06857            return null;
 6858        }
 6859
 6860        public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitD
 6861        {
 06862            if (!OperatingSystem.IsWindows()
 06863                || options.HardwareAccelerationType != HardwareAccelerationType.amf)
 6864            {
 06865                return null;
 6866            }
 6867
 06868            var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
 06869                && IsOpenclFullSupported()
 06870                && _mediaEncoder.SupportsFilter("alphasrc");
 06871            var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06872                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06873            var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 6874
 06875            if (is8bitSwFormatsAmf)
 6876            {
 06877                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06878                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6879                {
 06880                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6881                }
 6882
 06883                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6884                {
 06885                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6886                }
 6887
 06888                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6889                {
 06890                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6891                }
 6892            }
 6893
 06894            if (is8_10bitSwFormatsAmf)
 6895            {
 06896                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06897                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6898                {
 06899                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6900                }
 6901
 06902                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6903                {
 06904                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6905                }
 6906
 06907                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6908                {
 06909                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6910                }
 6911            }
 6912
 06913            return null;
 6914        }
 6915
 6916        public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6917        {
 06918            if (!OperatingSystem.IsLinux()
 06919                || options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 6920            {
 06921                return null;
 6922            }
 6923
 06924            var hwSurface = IsVaapiSupported(state)
 06925                && IsVaapiFullSupported()
 06926                && IsOpenclFullSupported()
 06927                && _mediaEncoder.SupportsFilter("alphasrc");
 06928            var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06929                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06930            var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06931            var is8_10_12bitSwFormatsVaapi = is8_10bitSwFormatsVaapi
 06932                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06933                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06934                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06935                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06936                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06937                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06938                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6939
 06940            if (is8bitSwFormatsVaapi)
 6941            {
 06942                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06943                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6944                {
 06945                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6946                }
 6947
 06948                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6949                {
 06950                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6951                }
 6952
 06953                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6954                {
 06955                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6956                }
 6957
 06958                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6959                {
 06960                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6961                }
 6962            }
 6963
 06964            if (is8_10bitSwFormatsVaapi)
 6965            {
 06966                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6967                {
 06968                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6969                }
 6970
 06971                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6972                {
 06973                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6974                }
 6975            }
 6976
 06977            if (is8_10_12bitSwFormatsVaapi)
 6978            {
 06979                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06980                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6981                {
 06982                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6983                }
 6984            }
 6985
 06986            return null;
 6987        }
 6988
 6989        public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream,
 6990        {
 06991            if (!OperatingSystem.IsMacOS()
 06992                || options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 6993            {
 06994                return null;
 6995            }
 6996
 06997            var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase
 06998                                    || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnore
 06999            var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, String
 07000            var is8_10_12bitSwFormatsVt = is8_10bitSwFormatsVt
 07001                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07002                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07003                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07004                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07005                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07006                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07007                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 07008            var isAv1SupportedSwFormatsVt = is8_10bitSwFormatsVt || string.Equals("yuv420p12le", videoStream.PixelFormat
 7009
 7010            // The related patches make videotoolbox hardware surface working is only available in jellyfin-ffmpeg 7.0.1
 07011            bool useHwSurface = (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface) && IsVideoToolboxFullSupp
 7012
 07013            if (is8bitSwFormatsVt)
 7014            {
 07015                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7016                {
 07017                    return GetHwaccelType(state, options, "vp8", bitDepth, useHwSurface);
 7018                }
 7019            }
 7020
 07021            if (is8_10bitSwFormatsVt)
 7022            {
 07023                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 07024                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7025                {
 07026                    return GetHwaccelType(state, options, "h264", bitDepth, useHwSurface);
 7027                }
 7028
 07029                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7030                {
 07031                    return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
 7032                }
 7033            }
 7034
 07035            if (is8_10_12bitSwFormatsVt)
 7036            {
 07037                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 07038                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7039                {
 07040                    return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
 7041                }
 7042
 07043                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 07044                    && isAv1SupportedSwFormatsVt
 07045                    && _mediaEncoder.IsVideoToolboxAv1DecodeAvailable)
 7046                {
 07047                    return GetHwaccelType(state, options, "av1", bitDepth, useHwSurface);
 7048                }
 7049            }
 7050
 07051            return null;
 7052        }
 7053
 7054        public string GetRkmppVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 7055        {
 07056            var isLinux = OperatingSystem.IsLinux();
 7057
 07058            if (!isLinux
 07059                || options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 7060            {
 07061                return null;
 7062            }
 7063
 07064            var inW = state.VideoStream?.Width;
 07065            var inH = state.VideoStream?.Height;
 07066            var reqW = state.BaseRequest.Width;
 07067            var reqH = state.BaseRequest.Height;
 07068            var reqMaxW = state.BaseRequest.MaxWidth;
 07069            var reqMaxH = state.BaseRequest.MaxHeight;
 7070
 7071            // rkrga RGA2e supports range from 1/16 to 16
 07072            if (!IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 16.0f))
 7073            {
 07074                return null;
 7075            }
 7076
 07077            var isRkmppOclSupported = IsRkmppFullSupported() && IsOpenclFullSupported();
 07078            var hwSurface = isRkmppOclSupported
 07079                && _mediaEncoder.SupportsFilter("alphasrc");
 7080
 7081            // rkrga RGA3 supports range from 1/8 to 8
 07082            var isAfbcSupported = hwSurface && IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 7083
 7084            // TODO: add more 8/10bit and 4:2:2 formats for Rkmpp after finishing the ffcheck tool
 07085            var is8bitSwFormatsRkmpp = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 07086                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 07087            var is10bitSwFormatsRkmpp = string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIg
 07088            var is8_10bitSwFormatsRkmpp = is8bitSwFormatsRkmpp || is10bitSwFormatsRkmpp;
 7089
 7090            // nv15 and nv20 are bit-stream only formats
 07091            if (is10bitSwFormatsRkmpp && !hwSurface)
 7092            {
 07093                return null;
 7094            }
 7095
 07096            if (is8bitSwFormatsRkmpp)
 7097            {
 07098                if (string.Equals(videoStream.Codec, "mpeg1video", StringComparison.OrdinalIgnoreCase))
 7099                {
 07100                    return GetHwaccelType(state, options, "mpeg1video", bitDepth, hwSurface);
 7101                }
 7102
 07103                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 7104                {
 07105                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 7106                }
 7107
 07108                if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 7109                {
 07110                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
 7111                }
 7112
 07113                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 7114                {
 07115                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 7116                }
 7117            }
 7118
 07119            if (is8_10bitSwFormatsRkmpp)
 7120            {
 07121                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 07122                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 7123                {
 07124                    var accelType = GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 07125                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7126                }
 7127
 07128                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 07129                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 7130                {
 07131                    var accelType = GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 07132                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7133                }
 7134
 07135                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 7136                {
 07137                    var accelType = GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 07138                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7139                }
 7140
 07141                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 7142                {
 7143                    // there's an issue about AV1 AFBC on RK3588, disable it for now until it's fixed upstream
 07144                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 7145                }
 7146            }
 7147
 07148            return null;
 7149        }
 7150
 7151        /// <summary>
 7152        /// Gets the number of threads.
 7153        /// </summary>
 7154        /// <param name="state">Encoding state.</param>
 7155        /// <param name="encodingOptions">Encoding options.</param>
 7156        /// <param name="outputVideoCodec">Video codec to use.</param>
 7157        /// <returns>Number of threads.</returns>
 7158#nullable enable
 7159        public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVide
 7160        {
 07161            var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount;
 7162
 07163            if (threads <= 0)
 7164            {
 7165                // Automatically set thread count
 07166                return 0;
 7167            }
 7168
 07169            return Math.Min(threads, Environment.ProcessorCount);
 7170        }
 7171
 7172#nullable disable
 7173        public void TryStreamCopy(EncodingJobInfo state, EncodingOptions options)
 7174        {
 07175            if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream))
 7176            {
 07177                state.OutputVideoCodec = "copy";
 7178            }
 7179            else
 7180            {
 07181                var user = state.User;
 7182
 7183                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07184                if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
 7185                {
 07186                    state.OutputVideoCodec = "copy";
 7187                }
 7188            }
 7189
 07190            var preventHlsAudioCopy = state.TranscodingType is TranscodingJobType.Hls
 07191                && state.VideoStream is not null
 07192                && !IsCopyCodec(state.OutputVideoCodec)
 07193                && options.HlsAudioSeekStrategy is HlsAudioSeekStrategy.TranscodeAudio;
 7194
 07195            if (state.AudioStream is not null
 07196                && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs)
 07197                && !preventHlsAudioCopy)
 7198            {
 07199                state.OutputAudioCodec = "copy";
 7200            }
 7201            else
 7202            {
 07203                var user = state.User;
 7204
 7205                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07206                if (user is not null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
 7207                {
 07208                    state.OutputAudioCodec = "copy";
 7209                }
 7210            }
 07211        }
 7212
 7213        private string GetFfmpegAnalyzeDurationArg(EncodingJobInfo state)
 7214        {
 47215            var analyzeDurationArgument = string.Empty;
 7216
 7217            // Apply -analyzeduration as per the environment variable,
 7218            // otherwise ffmpeg will break on certain files due to default value is 0.
 47219            var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
 7220
 47221            if (state.MediaSource.AnalyzeDurationMs > 0)
 7222            {
 07223                analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToStr
 7224            }
 47225            else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
 7226            {
 07227                analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
 7228            }
 7229
 47230            return analyzeDurationArgument;
 7231        }
 7232
 7233        private string GetFfmpegProbesizeArg()
 7234        {
 47235            var ffmpegProbeSize = _config.GetFFmpegProbeSize();
 7236
 47237            if (!string.IsNullOrEmpty(ffmpegProbeSize))
 7238            {
 07239                return $"-probesize {ffmpegProbeSize}";
 7240            }
 7241
 47242            return string.Empty;
 7243        }
 7244
 7245        public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
 7246        {
 07247            var inputModifier = string.Empty;
 07248            var analyzeDurationArgument = GetFfmpegAnalyzeDurationArg(state);
 7249
 07250            if (!string.IsNullOrEmpty(analyzeDurationArgument))
 7251            {
 07252                inputModifier += " " + analyzeDurationArgument;
 7253            }
 7254
 07255            inputModifier = inputModifier.Trim();
 7256
 7257            // Apply -probesize if configured
 07258            var ffmpegProbeSizeArgument = GetFfmpegProbesizeArg();
 7259
 07260            if (!string.IsNullOrEmpty(ffmpegProbeSizeArgument))
 7261            {
 07262                inputModifier += " " + ffmpegProbeSizeArgument;
 7263            }
 7264
 07265            var userAgentParam = GetUserAgentParam(state);
 7266
 07267            if (!string.IsNullOrEmpty(userAgentParam))
 7268            {
 07269                inputModifier += " " + userAgentParam;
 7270            }
 7271
 07272            inputModifier = inputModifier.Trim();
 7273
 07274            var refererParam = GetRefererParam(state);
 7275
 07276            if (!string.IsNullOrEmpty(refererParam))
 7277            {
 07278                inputModifier += " " + refererParam;
 7279            }
 7280
 07281            inputModifier = inputModifier.Trim();
 7282
 07283            inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions, segmentContainer);
 07284            inputModifier = inputModifier.Trim();
 7285
 07286            if (state.InputProtocol == MediaProtocol.Rtsp)
 7287            {
 07288                inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
 7289            }
 7290
 07291            if (!string.IsNullOrEmpty(state.InputAudioSync))
 7292            {
 07293                inputModifier += " -async " + state.InputAudioSync;
 7294            }
 7295
 7296            // The -fps_mode option cannot be applied to input
 07297            if (!string.IsNullOrEmpty(state.InputVideoSync) && _mediaEncoder.EncoderVersion < new Version(5, 1))
 7298            {
 07299                inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion);
 7300            }
 7301
 07302            int readrate = 0;
 07303            if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp)
 7304            {
 07305                readrate = 1;
 07306                inputModifier += " -re";
 7307            }
 07308            else if (encodingOptions.EnableSegmentDeletion
 07309                && state.VideoStream is not null
 07310                && state.TranscodingType == TranscodingJobType.Hls
 07311                && IsCopyCodec(state.OutputVideoCodec)
 07312                && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateOption)
 7313            {
 7314                // Set an input read rate limit 10x for using SegmentDeletion with stream-copy
 7315                // to prevent ffmpeg from exiting prematurely (due to fast drive)
 07316                readrate = 10;
 07317                inputModifier += $" -readrate {readrate}";
 7318            }
 7319
 7320            // Set a larger catchup value to revert to the old behavior,
 7321            // otherwise, remuxing might stall due to this new option
 07322            if (readrate > 0 && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateCatchupOption)
 7323            {
 07324                inputModifier += $" -readrate_catchup {readrate * 100}";
 7325            }
 7326
 07327            var flags = new List<string>();
 07328            if (state.IgnoreInputDts)
 7329            {
 07330                flags.Add("+igndts");
 7331            }
 7332
 07333            if (state.IgnoreInputIndex)
 7334            {
 07335                flags.Add("+ignidx");
 7336            }
 7337
 07338            if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
 7339            {
 07340                flags.Add("+genpts");
 7341            }
 7342
 07343            if (state.DiscardCorruptFramesInput)
 7344            {
 07345                flags.Add("+discardcorrupt");
 7346            }
 7347
 07348            if (state.EnableFastSeekInput)
 7349            {
 07350                flags.Add("+fastseek");
 7351            }
 7352
 07353            if (flags.Count > 0)
 7354            {
 07355                inputModifier += " -fflags " + string.Join(string.Empty, flags);
 7356            }
 7357
 07358            if (state.IsVideoRequest)
 7359            {
 07360                if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && encodingOpt
 7361                {
 07362                    var inputFormat = GetInputFormat(state.InputContainer);
 07363                    if (!string.IsNullOrEmpty(inputFormat))
 7364                    {
 07365                        inputModifier += " -f " + inputFormat;
 7366                    }
 7367                }
 7368            }
 7369
 07370            if (state.MediaSource.RequiresLooping)
 7371            {
 07372                inputModifier += " -stream_loop -1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2";
 7373            }
 7374
 07375            return inputModifier;
 7376        }
 7377
 7378        public void AttachMediaSourceInfo(
 7379            EncodingJobInfo state,
 7380            EncodingOptions encodingOptions,
 7381            MediaSourceInfo mediaSource,
 7382            string requestedUrl)
 7383        {
 07384            ArgumentNullException.ThrowIfNull(state);
 7385
 07386            ArgumentNullException.ThrowIfNull(mediaSource);
 7387
 07388            var path = mediaSource.Path;
 07389            var protocol = mediaSource.Protocol;
 7390
 07391            if (!string.IsNullOrEmpty(mediaSource.EncoderPath) && mediaSource.EncoderProtocol.HasValue)
 7392            {
 07393                path = mediaSource.EncoderPath;
 07394                protocol = mediaSource.EncoderProtocol.Value;
 7395            }
 7396
 07397            state.MediaPath = path;
 07398            state.InputProtocol = protocol;
 07399            state.InputContainer = mediaSource.Container;
 07400            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07401            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 7402
 07403            state.IsoType = mediaSource.IsoType;
 7404
 07405            if (mediaSource.Timestamp.HasValue)
 7406            {
 07407                state.InputTimestamp = mediaSource.Timestamp.Value;
 7408            }
 7409
 07410            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07411            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 07412            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
 7413
 07414            if ((state.ReadInputAtNativeFramerate && !state.IsSegmentedLiveStream)
 07415                || (mediaSource.Protocol == MediaProtocol.File
 07416                && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)))
 7417            {
 07418                state.InputVideoSync = "-1";
 07419                state.InputAudioSync = "1";
 7420            }
 7421
 07422            if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase)
 07423                || string.Equals(mediaSource.Container, "asf", StringComparison.OrdinalIgnoreCase))
 7424            {
 7425                // Seeing some stuttering when transcoding wma to audio-only HLS
 07426                state.InputAudioSync = "1";
 7427            }
 7428
 07429            var mediaStreams = mediaSource.MediaStreams;
 7430
 07431            if (state.IsVideoRequest)
 7432            {
 07433                var videoRequest = state.BaseRequest;
 7434
 07435                if (string.IsNullOrEmpty(videoRequest.VideoCodec))
 7436                {
 07437                    if (string.IsNullOrEmpty(requestedUrl))
 7438                    {
 07439                        requestedUrl = "test." + videoRequest.Container;
 7440                    }
 7441
 07442                    videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
 7443                }
 7444
 07445                state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
 07446                state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Su
 07447                state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
 07448                state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
 7449
 07450                if (state.SubtitleStream is not null && !state.SubtitleStream.IsExternal)
 7451                {
 07452                    state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !
 7453                }
 7454
 07455                EnforceResolutionLimit(state);
 7456
 07457                NormalizeSubtitleEmbed(state);
 7458            }
 7459            else
 7460            {
 07461                state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
 7462            }
 7463
 07464            state.MediaSource = mediaSource;
 7465
 07466            var request = state.BaseRequest;
 07467            var supportedAudioCodecs = state.SupportedAudioCodecs;
 07468            if (request is not null && supportedAudioCodecs is not null && supportedAudioCodecs.Length > 0)
 7469            {
 07470                var supportedAudioCodecsList = supportedAudioCodecs.ToList();
 7471
 07472                ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
 7473
 07474                state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray();
 7475
 07476                request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(_mediaEncoder.CanEncodeToAudioCodec)
 07477                    ?? state.SupportedAudioCodecs.FirstOrDefault();
 7478            }
 7479
 07480            var supportedVideoCodecs = state.SupportedVideoCodecs;
 07481            if (request is not null && supportedVideoCodecs is not null && supportedVideoCodecs.Length > 0)
 7482            {
 07483                var supportedVideoCodecsList = supportedVideoCodecs.ToList();
 7484
 07485                ShiftVideoCodecsIfNeeded(supportedVideoCodecsList, encodingOptions);
 7486
 07487                state.SupportedVideoCodecs = supportedVideoCodecsList.ToArray();
 7488
 07489                request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
 7490            }
 07491        }
 7492
 7493        private void ShiftAudioCodecsIfNeeded(List<string> audioCodecs, MediaStream audioStream)
 7494        {
 7495            // No need to shift if there is only one supported audio codec.
 07496            if (audioCodecs.Count < 2)
 7497            {
 07498                return;
 7499            }
 7500
 07501            var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
 07502            var shiftAudioCodecs = new List<string>();
 07503            if (inputChannels >= 6)
 7504            {
 7505                // DTS and TrueHD are not supported by HLS
 7506                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07507                shiftAudioCodecs.Add("dts");
 07508                shiftAudioCodecs.Add("truehd");
 7509            }
 7510            else
 7511            {
 7512                // Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
 7513                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07514                shiftAudioCodecs.Add("ac3");
 07515                shiftAudioCodecs.Add("eac3");
 7516            }
 7517
 07518            if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7519            {
 07520                return;
 7521            }
 7522
 07523            while (shiftAudioCodecs.Contains(audioCodecs[0], StringComparison.OrdinalIgnoreCase))
 7524            {
 07525                var removed = audioCodecs[0];
 07526                audioCodecs.RemoveAt(0);
 07527                audioCodecs.Add(removed);
 7528            }
 07529        }
 7530
 7531        private void ShiftVideoCodecsIfNeeded(List<string> videoCodecs, EncodingOptions encodingOptions)
 7532        {
 7533            // No need to shift if there is only one supported video codec.
 07534            if (videoCodecs.Count < 2)
 7535            {
 07536                return;
 7537            }
 7538
 7539            // Shift codecs to the end of list if it's not allowed.
 07540            var shiftVideoCodecs = new List<string>();
 07541            if (!encodingOptions.AllowHevcEncoding)
 7542            {
 07543                shiftVideoCodecs.Add("hevc");
 07544                shiftVideoCodecs.Add("h265");
 7545            }
 7546
 07547            if (!encodingOptions.AllowAv1Encoding)
 7548            {
 07549                shiftVideoCodecs.Add("av1");
 7550            }
 7551
 07552            if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7553            {
 07554                return;
 7555            }
 7556
 07557            while (shiftVideoCodecs.Contains(videoCodecs[0], StringComparison.OrdinalIgnoreCase))
 7558            {
 07559                var removed = videoCodecs[0];
 07560                videoCodecs.RemoveAt(0);
 07561                videoCodecs.Add(removed);
 7562            }
 07563        }
 7564
 7565        private void NormalizeSubtitleEmbed(EncodingJobInfo state)
 7566        {
 07567            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7568            {
 07569                return;
 7570            }
 7571
 7572            // This is tricky to remux in, after converting to dvdsub it's not positioned correctly
 7573            // Therefore, let's just burn it in
 07574            if (string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 7575            {
 07576                state.SubtitleDeliveryMethod = SubtitleDeliveryMethod.Encode;
 7577            }
 07578        }
 7579
 7580        public string GetSubtitleEmbedArguments(EncodingJobInfo state)
 7581        {
 07582            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7583            {
 07584                return string.Empty;
 7585            }
 7586
 07587            var format = state.SupportedSubtitleCodecs.FirstOrDefault();
 7588            string codec;
 7589
 07590            if (string.IsNullOrEmpty(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.Ordin
 7591            {
 07592                codec = "copy";
 7593            }
 7594            else
 7595            {
 07596                codec = format;
 7597            }
 7598
 07599            return " -codec:s:0 " + codec + " -disposition:s:0 default";
 7600        }
 7601
 7602        public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, Encoder
 7603        {
 7604            // Get the output codec name
 07605            var videoCodec = GetVideoEncoder(state, encodingOptions);
 7606
 07607            var format = string.Empty;
 07608            var keyFrame = string.Empty;
 07609            var outputPath = state.OutputFilePath;
 7610
 07611            if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase)
 07612                && state.BaseRequest.Context == EncodingContext.Streaming)
 7613            {
 7614                // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
 07615                format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov";
 7616            }
 7617
 07618            var threads = GetNumberOfThreads(state, encodingOptions, videoCodec);
 7619
 07620            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7621
 07622            return string.Format(
 07623                CultureInfo.InvariantCulture,
 07624                "{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
 07625                inputModifier,
 07626                GetInputArgument(state, encodingOptions, null),
 07627                keyFrame,
 07628                GetMapArgs(state),
 07629                GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset),
 07630                threads,
 07631                GetProgressiveVideoAudioArguments(state, encodingOptions),
 07632                GetSubtitleEmbedArguments(state),
 07633                format,
 07634                outputPath).Trim();
 7635        }
 7636
 7637        public string GetOutputFFlags(EncodingJobInfo state)
 7638        {
 07639            var flags = new List<string>();
 07640            if (state.GenPtsOutput)
 7641            {
 07642                flags.Add("+genpts");
 7643            }
 7644
 07645            if (flags.Count > 0)
 7646            {
 07647                return " -fflags " + string.Join(string.Empty, flags);
 7648            }
 7649
 07650            return string.Empty;
 7651        }
 7652
 7653        public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoC
 7654        {
 07655            var args = "-codec:v:0 " + videoCodec;
 7656
 07657            if (state.BaseRequest.EnableMpegtsM2TsMode)
 7658            {
 07659                args += " -mpegts_m2ts_mode 1";
 7660            }
 7661
 07662            if (IsCopyCodec(videoCodec))
 7663            {
 07664                if (state.VideoStream is not null
 07665                    && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
 07666                    && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
 7667                {
 07668                    string bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Video);
 07669                    if (!string.IsNullOrEmpty(bitStreamArgs))
 7670                    {
 07671                        args += " " + bitStreamArgs;
 7672                    }
 7673                }
 7674
 07675                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7676                {
 07677                    args += " -copyts -avoid_negative_ts disabled -start_at_zero";
 7678                }
 7679
 07680                if (!state.RunTimeTicks.HasValue)
 7681                {
 07682                    args += " -fflags +genpts";
 7683                }
 7684            }
 7685            else
 7686            {
 07687                var keyFrameArg = string.Format(
 07688                    CultureInfo.InvariantCulture,
 07689                    " -force_key_frames \"expr:gte(t,n_forced*{0})\"",
 07690                    5);
 7691
 07692                args += keyFrameArg;
 7693
 07694                var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream &&
 7695
 07696                var hasCopyTs = false;
 7697
 7698                // video processing filters.
 07699                var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
 7700
 07701                var negativeMapArgs = GetNegativeMapArgsByFilters(state, videoProcessParam);
 7702
 07703                args = negativeMapArgs + args + videoProcessParam;
 7704
 07705                hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
 7706
 07707                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7708                {
 07709                    if (!hasCopyTs)
 7710                    {
 07711                        args += " -copyts";
 7712                    }
 7713
 07714                    args += " -avoid_negative_ts disabled";
 7715
 07716                    if (!(state.SubtitleStream is not null && state.SubtitleStream.IsExternal && !state.SubtitleStream.I
 7717                    {
 07718                        args += " -start_at_zero";
 7719                    }
 7720                }
 7721
 07722                var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset);
 7723
 07724                if (!string.IsNullOrEmpty(qualityParam))
 7725                {
 07726                    args += " " + qualityParam.Trim();
 7727                }
 7728            }
 7729
 07730            if (!string.IsNullOrEmpty(state.OutputVideoSync))
 7731            {
 07732                args += GetVideoSyncOption(state.OutputVideoSync, _mediaEncoder.EncoderVersion);
 7733            }
 7734
 07735            args += GetOutputFFlags(state);
 7736
 07737            return args;
 7738        }
 7739
 7740        public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions)
 7741        {
 7742            // If the video doesn't have an audio stream, return a default.
 07743            if (state.AudioStream is null && state.VideoStream is not null)
 7744            {
 07745                return string.Empty;
 7746            }
 7747
 7748            // Get the output codec name
 07749            var codec = GetAudioEncoder(state);
 7750
 07751            var args = "-codec:a:0 " + codec;
 7752
 07753            if (IsCopyCodec(codec))
 7754            {
 07755                return args;
 7756            }
 7757
 07758            var channels = state.OutputAudioChannels;
 7759
 07760            var useDownMixAlgorithm = state.AudioStream is not null
 07761                                      && DownMixAlgorithmsHelper.AlgorithmFilterStrings.ContainsKey((encodingOptions.Dow
 7762
 07763            if (channels.HasValue && !useDownMixAlgorithm)
 7764            {
 07765                args += " -ac " + channels.Value;
 7766            }
 7767
 07768            var bitrate = state.OutputAudioBitrate;
 07769            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
 7770            {
 07771                var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2);
 07772                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7773                {
 07774                    args += vbrParam;
 7775                }
 7776                else
 7777                {
 07778                    args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
 7779                }
 7780            }
 7781
 07782            if (state.OutputAudioSampleRate.HasValue)
 7783            {
 07784                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
 7785            }
 7786
 07787            args += GetAudioFilterParam(state, encodingOptions);
 7788
 07789            return args;
 7790        }
 7791
 7792        public string GetProgressiveAudioFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string 
 7793        {
 07794            var audioTranscodeParams = new List<string>();
 7795
 07796            var bitrate = state.OutputAudioBitrate;
 07797            var channels = state.OutputAudioChannels;
 07798            var outputCodec = state.OutputAudioCodec;
 7799
 07800            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
 7801            {
 07802                var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2);
 07803                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7804                {
 07805                    audioTranscodeParams.Add(vbrParam);
 7806                }
 7807                else
 7808                {
 07809                    audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
 7810                }
 7811            }
 7812
 07813            if (channels.HasValue)
 7814            {
 07815                audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)
 7816            }
 7817
 07818            if (!string.IsNullOrEmpty(outputCodec))
 7819            {
 07820                audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
 7821            }
 7822
 07823            if (GetAudioEncoder(state).StartsWith("pcm_", StringComparison.Ordinal))
 7824            {
 07825                audioTranscodeParams.Add(string.Concat("-f ", GetAudioEncoder(state).AsSpan(4)));
 07826                audioTranscodeParams.Add("-ar " + state.BaseRequest.AudioBitRate);
 7827            }
 7828
 07829            if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
 7830            {
 7831                // opus only supports specific sampling rates
 07832                var sampleRate = state.OutputAudioSampleRate;
 07833                if (sampleRate.HasValue)
 7834                {
 07835                    var sampleRateValue = sampleRate.Value switch
 07836                    {
 07837                        <= 8000 => 8000,
 07838                        <= 12000 => 12000,
 07839                        <= 16000 => 16000,
 07840                        <= 24000 => 24000,
 07841                        _ => 48000
 07842                    };
 7843
 07844                    audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
 7845                }
 7846            }
 7847
 7848            // Copy the movflags from GetProgressiveVideoFullCommandLine
 7849            // See #9248 and the associated PR for why this is needed
 07850            if (_mp4ContainerNames.Contains(state.OutputContainer))
 7851            {
 07852                audioTranscodeParams.Add("-movflags empty_moov+delay_moov");
 7853            }
 7854
 07855            var threads = GetNumberOfThreads(state, encodingOptions, null);
 7856
 07857            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7858
 07859            return string.Format(
 07860                CultureInfo.InvariantCulture,
 07861                "{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"",
 07862                inputModifier,
 07863                GetInputArgument(state, encodingOptions, null),
 07864                threads,
 07865                " -vn",
 07866                string.Join(' ', audioTranscodeParams),
 07867                outputPath,
 07868                string.Empty,
 07869                string.Empty,
 07870                string.Empty).Trim();
 7871        }
 7872
 7873        public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
 7874        {
 167875            var index = 0;
 167876            var length = mediaStreams.Count;
 7877
 567878            for (var i = 0; i < length; i++)
 7879            {
 287880                var currentMediaStream = mediaStreams[i];
 287881                if (currentMediaStream == streamToFind)
 7882                {
 167883                    return index;
 7884                }
 7885
 127886                if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
 7887                {
 127888                    index++;
 7889                }
 7890            }
 7891
 07892            return -1;
 7893        }
 7894
 7895        public static bool IsCopyCodec(string codec)
 7896        {
 87897            return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
 7898        }
 7899
 7900        private static bool ShouldEncodeSubtitle(EncodingJobInfo state)
 7901        {
 107902            return state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
 107903                   || (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec));
 7904        }
 7905
 7906        private static bool NeedsExternalSubtitleMuxing(EncodingJobInfo state)
 7907        {
 47908            return state.SubtitleStream is not null
 47909                && state.SubtitleStream.IsExternal
 47910                && (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed
 47911                    || (ShouldEncodeSubtitle(state) && !state.SubtitleStream.IsTextSubtitleStream));
 7912        }
 7913
 7914        public static string GetVideoSyncOption(string videoSync, Version encoderVersion)
 7915        {
 07916            if (string.IsNullOrEmpty(videoSync))
 7917            {
 07918                return string.Empty;
 7919            }
 7920
 07921            if (encoderVersion >= new Version(5, 1))
 7922            {
 07923                if (int.TryParse(videoSync, CultureInfo.InvariantCulture, out var vsync))
 7924                {
 07925                    return vsync switch
 07926                    {
 07927                        -1 => " -fps_mode auto",
 07928                        0 => " -fps_mode passthrough",
 07929                        1 => " -fps_mode cfr",
 07930                        2 => " -fps_mode vfr",
 07931                        _ => string.Empty
 07932                    };
 7933                }
 7934
 07935                return string.Empty;
 7936            }
 7937
 7938            // -vsync is deprecated in FFmpeg 5.1 and will be removed in the future.
 07939            return $" -vsync {videoSync}";
 7940        }
 7941    }
 7942}

Methods/Properties

.ctor(MediaBrowser.Common.Configuration.IApplicationPaths,MediaBrowser.Controller.MediaEncoding.IMediaEncoder,MediaBrowser.Controller.MediaEncoding.ISubtitleEncoder,Microsoft.Extensions.Configuration.IConfiguration,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.IO.IPathManager)
.cctor()
GetH264Encoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetH265Encoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetAv1Encoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetH26xOrAv1Encoder(System.String,System.String,MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetMjpegEncoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
IsVaapiSupported(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
IsVaapiFullSupported()
IsRkmppFullSupported()
IsOpenclFullSupported()
IsCudaFullSupported()
IsVulkanFullSupported()
IsVideoToolboxFullSupported()
IsSwTonemapAvailable(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
IsHwTonemapAvailable(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
IsVulkanHwTonemapAvailable(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
IsIntelVppTonemapAvailable(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
IsVideoToolboxTonemapAvailable(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
IsVideoStreamHevcRext(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetVideoEncoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetUserAgentParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetRefererParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetInputFormat(System.String)
GetDecoderFromCodec(System.String)
InferAudioCodec(System.String)
InferVideoCodec(System.String)
GetVideoProfileScore(System.String,System.String)
GetAudioEncoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetRkmppDeviceArgs(System.String)
GetVideoToolboxDeviceArgs(System.String)
GetCudaDeviceArgs(System.Int32,System.String)
GetVulkanDeviceArgs(System.Int32,System.String,System.String,System.String)
GetOpenclDeviceArgs(System.Int32,System.String,System.String,System.String)
GetD3d11vaDeviceArgs(System.Int32,System.String,System.String)
GetVaapiDeviceArgs(System.String,System.String,System.String,System.String,System.String,System.String)
GetDrmDeviceArgs(System.String,System.String)
GetQsvDeviceArgs(System.String,System.String)
GetFilterHwDeviceArgs(System.String)
GetGraphicalSubCanvasSize(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetInputVideoHwaccelArgs(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetInputArgument(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
IsH264(MediaBrowser.Model.Entities.MediaStream)
IsH265(MediaBrowser.Model.Entities.MediaStream)
IsAv1(MediaBrowser.Model.Entities.MediaStream)
IsAAC(MediaBrowser.Model.Entities.MediaStream)
IsDoviWithHdr10Bl(MediaBrowser.Model.Entities.MediaStream)
IsDovi(MediaBrowser.Model.Entities.MediaStream)
IsHdr10Plus(MediaBrowser.Model.Entities.MediaStream)
ShouldRemoveDynamicHdrMetadata(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
CanEncoderRemoveDynamicHdrMetadata(MediaBrowser.Controller.MediaEncoding.EncodingHelper/DynamicHdrMetadataRemovalPlan,MediaBrowser.Model.Entities.MediaStream)
IsDoviRemoved(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
IsHdr10PlusRemoved(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetBitStreamArgs(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Entities.MediaStreamType)
GetAudioBitStreamArguments(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.String,System.String)
GetSegmentFileExtension(System.String)
GetVideoBitrateParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.String)
GetEncoderParam(System.Nullable`1<MediaBrowser.Model.Entities.EncoderPreset>,MediaBrowser.Model.Entities.EncoderPreset,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.Boolean)
NormalizeTranscodingLevel(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.String)
GetTextSubtitlesFilter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.Boolean,System.Boolean)
GetFramerateParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetHlsVideoKeyFrameArguments(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.String,System.Int32,System.Boolean,System.Nullable`1<System.Int32>)
GetVideoQualityParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.String,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.EncoderPreset)
CanStreamCopyVideo(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Entities.MediaStream)
CanStreamCopyAudio(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Entities.MediaStream,System.Collections.Generic.IEnumerable`1<System.String>)
GetVideoBitrateParamValue(MediaBrowser.Controller.MediaEncoding.BaseEncodingJobOptions,MediaBrowser.Model.Entities.MediaStream,System.String)
GetMinBitrate(System.Int32,System.Int32)
GetVideoBitrateScaleFactor(System.String)
ScaleBitrate(System.Int32,System.String,System.String)
GetAudioBitrateParam(MediaBrowser.Controller.MediaEncoding.BaseEncodingJobOptions,MediaBrowser.Model.Entities.MediaStream,System.Nullable`1<System.Int32>)
GetAudioBitrateParam(System.Nullable`1<System.Int32>,System.String,MediaBrowser.Model.Entities.MediaStream,System.Nullable`1<System.Int32>)
GetAudioVbrModeParam(System.String,System.Int32,System.Int32)
GetAudioFilterParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetNumAudioChannelsParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Entities.MediaStream,System.String)
EnforceResolutionLimit(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetFastSeekCommandLineParameter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetMapArgs(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetNegativeMapArgsByFilters(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.String)
GetMediaStream(System.Collections.Generic.IEnumerable`1<MediaBrowser.Model.Entities.MediaStream>,System.Nullable`1<System.Int32>,MediaBrowser.Model.Entities.MediaStreamType,System.Boolean)
GetFixedOutputSize(System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>)
IsScaleRatioSupported(System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Double>)
GetHwScaleFilter(System.String,System.String,System.String,System.Boolean,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>)
GetGraphicalSubPreProcessFilters(System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>)
GetAlphaSrcFilter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Single>)
GetSwScaleFilter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<MediaBrowser.Model.Entities.Video3DFormat>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>)
GetFixedSwScaleFilter(System.Nullable`1<MediaBrowser.Model.Entities.Video3DFormat>,System.Int32,System.Int32)
GetSwDeinterlaceFilter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetHwDeinterlaceFilter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetHwTonemapFilter(MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String,System.Boolean)
GetLibplaceboFilter(MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.Boolean,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Boolean)
GetVideoTransposeDirection(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetSwVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetNvidiaVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetNvidiaVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetAmdVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetAmdDx11VidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetIntelVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetIntelQsvDx11VidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetIntelQsvVaapiVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetVaapiVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetIntelVaapiFullVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetAmdVaapiFullVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetVaapiLimitedVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetAppleVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetAppleVidFiltersPreferred(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetRkmppVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetRkmppVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetVideoProcessingFilterParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetOverwriteColorPropertiesParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.Boolean)
GetInputHdrParam(System.String)
GetOutputSdrParam(System.String)
GetVideoColorBitDepth(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetHardwareVideoDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetHwDecoderName(MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String,System.String,System.Int32)
GetHwaccelType(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.Int32,System.Boolean)
GetQsvHwVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetNvdecVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetAmfVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetVaapiVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetVideotoolboxVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetRkmppVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetNumberOfThreads(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
TryStreamCopy(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetFfmpegAnalyzeDurationArg(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetFfmpegProbesizeArg()
GetInputModifier(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
AttachMediaSourceInfo(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Dto.MediaSourceInfo,System.String)
ShiftAudioCodecsIfNeeded(System.Collections.Generic.List`1<System.String>,MediaBrowser.Model.Entities.MediaStream)
ShiftVideoCodecsIfNeeded(System.Collections.Generic.List`1<System.String>,MediaBrowser.Model.Configuration.EncodingOptions)
NormalizeSubtitleEmbed(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetSubtitleEmbedArguments(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetProgressiveVideoFullCommandLine(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.EncoderPreset)
GetOutputFFlags(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetProgressiveVideoArguments(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,MediaBrowser.Model.Entities.EncoderPreset)
GetProgressiveVideoAudioArguments(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetProgressiveAudioFullCommandLine(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
FindIndex(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Model.Entities.MediaStream>,MediaBrowser.Model.Entities.MediaStream)
IsCopyCodec(System.String)
ShouldEncodeSubtitle(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
NeedsExternalSubtitleMuxing(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetVideoSyncOption(System.String,System.Version)