< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.MediaEncoding.EncodingHelper
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
Line coverage
0%
Covered lines: 27
Uncovered lines: 3738
Coverable lines: 3765
Total lines: 7905
Line coverage: 0.7%
Branch coverage
0%
Covered branches: 0
Total branches: 3757
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 1/23/2026 - 12:11:06 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: 7905 1/23/2026 - 12:11:06 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: 7905

Coverage delta

Coverage delta 1 -1

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(...)0%210140%
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(...)0%420200%
GetInputVideoHwaccelArgs(...)0%145201200%
GetInputArgument(...)0%1482380%
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%1980440%
GetEncoderParam(...)0%8010890%
NormalizeTranscodingLevel(...)0%506220%
GetTextSubtitlesFilter(...)0%210140%
GetFramerateParam(...)0%156120%
GetHlsVideoKeyFrameArguments(...)0%1806420%
GetVideoQualityParam(...)0%325801800%
CanStreamCopyVideo(...)0%175561320%
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(...)0%506220%
GetMapArgs(...)0%1640400%
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(...)0%3906620%
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(...)0%4260%
GetFfmpegProbesizeArg()0%620%
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(...)0%4260%
IsCopyCodec(...)100%210%
ShouldEncodeSubtitle(...)0%2040%
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)
 2168        private readonly Version _minKerneli915Hang = new Version(5, 18);
 2169        private readonly Version _maxKerneli915Hang = new Version(6, 1, 3);
 2170        private readonly Version _minFixedKernel60i915Hang = new Version(6, 0, 18);
 2171        private readonly Version _minKernelVersionAmdVkFmtModifier = new Version(5, 15);
 72
 2173        private readonly Version _minFFmpegImplicitHwaccel = new Version(6, 0);
 2174        private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0);
 2175        private readonly Version _minFFmpegOclCuTonemapMode = new Version(5, 1, 3);
 2176        private readonly Version _minFFmpegSvtAv1Params = new Version(5, 1);
 2177        private readonly Version _minFFmpegVaapiH26xEncA53CcSei = new Version(6, 0);
 2178        private readonly Version _minFFmpegReadrateOption = new Version(5, 0);
 2179        private readonly Version _minFFmpegWorkingVtHwSurface = new Version(7, 0, 1);
 2180        private readonly Version _minFFmpegDisplayRotationOption = new Version(6, 0);
 2181        private readonly Version _minFFmpegAdvancedTonemapMode = new Version(7, 0, 1);
 2182        private readonly Version _minFFmpegAlteredVaVkInterop = new Version(7, 0, 1);
 2183        private readonly Version _minFFmpegQsvVppTonemapOption = new Version(7, 0, 1);
 2184        private readonly Version _minFFmpegQsvVppOutRangeOption = new Version(7, 0, 1);
 2185        private readonly Version _minFFmpegVaapiDeviceVendorId = new Version(7, 0, 1);
 2186        private readonly Version _minFFmpegQsvVppScaleModeOption = new Version(6, 0);
 2187        private readonly Version _minFFmpegRkmppHevcDecDoviRpu = new Version(7, 1, 1);
 2188        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        {
 21167            _appPaths = appPaths;
 21168            _mediaEncoder = mediaEncoder;
 21169            _subtitleEncoder = subtitleEncoder;
 21170            _config = config;
 21171            _configurationManager = configurationManager;
 21172            _pathManager = pathManager;
 21173        }
 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        {
 0473            var codec = state.OutputVideoCodec;
 474
 0475            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
 0504            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
 0972            if (state.SubtitleStream is not null
 0973                && ShouldEncodeSubtitle(state)
 0974                && !state.SubtitleStream.IsTextSubtitleStream
 0975                && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 976            {
 0977                var subtitleWidth = state.SubtitleStream?.Width;
 0978                var subtitleHeight = state.SubtitleStream?.Height;
 979
 0980                if (subtitleWidth.HasValue
 0981                    && subtitleHeight.HasValue
 0982                    && subtitleWidth.Value > 0
 0983                    && 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
 0993            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        {
 01004            if (!state.IsVideoRequest)
 1005            {
 01006                return string.Empty;
 1007            }
 1008
 01009            var vidEncoder = GetVideoEncoder(state, options) ?? string.Empty;
 01010            if (IsCopyCodec(vidEncoder))
 1011            {
 01012                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        {
 01238            var arg = new StringBuilder();
 01239            var inputVidHwaccelArgs = GetInputVideoHwaccelArgs(state, options);
 1240
 01241            if (!string.IsNullOrEmpty(inputVidHwaccelArgs))
 1242            {
 01243                arg.Append(inputVidHwaccelArgs);
 1244            }
 1245
 01246            var canvasArgs = GetGraphicalSubCanvasSize(state);
 01247            if (!string.IsNullOrEmpty(canvasArgs))
 1248            {
 01249                arg.Append(canvasArgs);
 1250            }
 1251
 01252            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            {
 01266                arg.Append(" -i ")
 01267                    .Append(_mediaEncoder.GetInputPathArgument(state));
 1268            }
 1269
 1270            // sub2video for external graphical subtitles
 01271            if (state.SubtitleStream is not null
 01272                && ShouldEncodeSubtitle(state)
 01273                && !state.SubtitleStream.IsTextSubtitleStream
 01274                && state.SubtitleStream.IsExternal)
 1275            {
 01276                var subtitlePath = state.SubtitleStream.Path;
 01277                var subtitleExtension = Path.GetExtension(subtitlePath.AsSpan());
 1278
 1279                // dvdsub/vobsub graphical subtitles use .sub+.idx pairs
 01280                if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase))
 1281                {
 01282                    var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
 01283                    if (File.Exists(idxFile))
 1284                    {
 01285                        subtitlePath = idxFile;
 1286                    }
 1287                }
 1288
 1289                // Use analyzeduration also for subtitle streams to improve resolution detection  with streams inside MK
 01290                var analyzeDurationArgument = GetFfmpegAnalyzeDurationArg(state);
 01291                if (!string.IsNullOrEmpty(analyzeDurationArgument))
 1292                {
 01293                    arg.Append(' ').Append(analyzeDurationArgument);
 1294                }
 1295
 1296                // Apply probesize, too, if configured
 01297                var ffmpegProbeSizeArgument = GetFfmpegProbesizeArg();
 01298                if (!string.IsNullOrEmpty(ffmpegProbeSizeArgument))
 1299                {
 01300                    arg.Append(' ').Append(ffmpegProbeSizeArgument);
 1301                }
 1302
 1303                // Also seek the external subtitles stream.
 01304                var seekSubParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01305                if (!string.IsNullOrEmpty(seekSubParam))
 1306                {
 01307                    arg.Append(' ').Append(seekSubParam);
 1308                }
 1309
 01310                if (!string.IsNullOrEmpty(canvasArgs))
 1311                {
 01312                    arg.Append(canvasArgs);
 1313                }
 1314
 01315                arg.Append(" -i file:\"").Append(subtitlePath).Append('\"');
 1316            }
 1317
 01318            if (state.AudioStream is not null && state.AudioStream.IsExternal)
 1319            {
 1320                // Also seek the external audio stream.
 01321                var seekAudioParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01322                if (!string.IsNullOrEmpty(seekAudioParam))
 1323                {
 01324                    arg.Append(' ').Append(seekAudioParam);
 1325                }
 1326
 01327                arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
 1328            }
 1329
 1330            // Disable auto inserted SW scaler for HW decoders in case of changed resolution.
 01331            var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
 01332            if (!isSwDecoder)
 1333            {
 01334                arg.Append(" -noautoscale");
 1335            }
 1336
 01337            return arg.ToString();
 1338        }
 1339
 1340        /// <summary>
 1341        /// Determines whether the specified stream is H264.
 1342        /// </summary>
 1343        /// <param name="stream">The stream.</param>
 1344        /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
 1345        public static bool IsH264(MediaStream stream)
 1346        {
 01347            var codec = stream.Codec ?? string.Empty;
 1348
 01349            return codec.Contains("264", StringComparison.OrdinalIgnoreCase)
 01350                    || codec.Contains("avc", StringComparison.OrdinalIgnoreCase);
 1351        }
 1352
 1353        public static bool IsH265(MediaStream stream)
 1354        {
 01355            var codec = stream.Codec ?? string.Empty;
 1356
 01357            return codec.Contains("265", StringComparison.OrdinalIgnoreCase)
 01358                || codec.Contains("hevc", StringComparison.OrdinalIgnoreCase);
 1359        }
 1360
 1361        public static bool IsAv1(MediaStream stream)
 1362        {
 01363            var codec = stream.Codec ?? string.Empty;
 1364
 01365            return codec.Contains("av1", StringComparison.OrdinalIgnoreCase);
 1366        }
 1367
 1368        public static bool IsAAC(MediaStream stream)
 1369        {
 01370            var codec = stream.Codec ?? string.Empty;
 1371
 01372            return codec.Contains("aac", StringComparison.OrdinalIgnoreCase);
 1373        }
 1374
 1375        public static bool IsDoviWithHdr10Bl(MediaStream stream)
 1376        {
 01377            var rangeType = stream?.VideoRangeType;
 1378
 01379            return rangeType is VideoRangeType.DOVIWithHDR10
 01380                or VideoRangeType.DOVIWithEL
 01381                or VideoRangeType.DOVIWithHDR10Plus
 01382                or VideoRangeType.DOVIWithELHDR10Plus
 01383                or VideoRangeType.DOVIInvalid;
 1384        }
 1385
 1386        public static bool IsDovi(MediaStream stream)
 1387        {
 01388            var rangeType = stream?.VideoRangeType;
 1389
 01390            return IsDoviWithHdr10Bl(stream)
 01391                   || (rangeType is VideoRangeType.DOVI
 01392                       or VideoRangeType.DOVIWithHLG
 01393                       or VideoRangeType.DOVIWithSDR);
 1394        }
 1395
 1396        public static bool IsHdr10Plus(MediaStream stream)
 1397        {
 01398            var rangeType = stream?.VideoRangeType;
 1399
 01400            return rangeType is VideoRangeType.HDR10Plus
 01401                       or VideoRangeType.DOVIWithHDR10Plus
 01402                       or VideoRangeType.DOVIWithELHDR10Plus;
 1403        }
 1404
 1405        /// <summary>
 1406        /// Check if dynamic HDR metadata should be removed during stream copy.
 1407        /// Please note this check assumes the range check has already been done
 1408        /// and trivial fallbacks like HDR10+ to HDR10, DOVIWithHDR10 to HDR10 is already checked.
 1409        /// </summary>
 1410        private static DynamicHdrMetadataRemovalPlan ShouldRemoveDynamicHdrMetadata(EncodingJobInfo state)
 1411        {
 01412            var videoStream = state.VideoStream;
 01413            if (videoStream.VideoRange is not VideoRange.HDR)
 1414            {
 01415                return DynamicHdrMetadataRemovalPlan.None;
 1416            }
 1417
 01418            var requestedRangeTypes = state.GetRequestedRangeTypes(state.VideoStream.Codec);
 01419            if (requestedRangeTypes.Length == 0)
 1420            {
 01421                return DynamicHdrMetadataRemovalPlan.None;
 1422            }
 1423
 01424            var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ordinal
 01425            var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIg
 01426            var requestHasDOVIwithEL = requestedRangeTypes.Contains(VideoRangeType.DOVIWithEL.ToString(), StringComparis
 01427            var requestHasDOVIwithELHDR10plus = requestedRangeTypes.Contains(VideoRangeType.DOVIWithELHDR10Plus.ToString
 1428
 01429            var shouldRemoveHdr10Plus = false;
 1430            // Case 1: Client supports HDR10, does not support DOVI with EL but EL presets
 01431            var shouldRemoveDovi = (!requestHasDOVIwithEL && requestHasHDR10) && videoStream.VideoRangeType == VideoRang
 1432
 1433            // Case 2: Client supports DOVI, does not support broken DOVI config
 1434            // Client does not report DOVI support should be allowed to copy bad data for remuxing as HDR10 players woul
 01435            shouldRemoveDovi = shouldRemoveDovi || (requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVII
 1436
 1437            // Special case: we have a video with both EL and HDR10+
 1438            // If the client supports EL but not in the case of coexistence with HDR10+, remove HDR10+ for compatibility
 1439            // Otherwise, remove DOVI if the client is not a DOVI player
 01440            if (videoStream.VideoRangeType == VideoRangeType.DOVIWithELHDR10Plus)
 1441            {
 01442                shouldRemoveHdr10Plus = requestHasDOVIwithEL && !requestHasDOVIwithELHDR10plus;
 01443                shouldRemoveDovi = shouldRemoveDovi || !shouldRemoveHdr10Plus;
 1444            }
 1445
 01446            if (shouldRemoveDovi)
 1447            {
 01448                return DynamicHdrMetadataRemovalPlan.RemoveDovi;
 1449            }
 1450
 1451            // If the client is a Dolby Vision Player, remove the HDR10+ metadata to avoid playback issues
 01452            shouldRemoveHdr10Plus = shouldRemoveHdr10Plus || (requestHasDOVI && videoStream.VideoRangeType == VideoRange
 01453            return shouldRemoveHdr10Plus ? DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus : DynamicHdrMetadataRemovalPlan
 1454        }
 1455
 1456        private bool CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan plan, MediaStream videoStream)
 1457        {
 01458            return plan switch
 01459            {
 01460                DynamicHdrMetadataRemovalPlan.RemoveDovi => _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFil
 01461                                                            || (IsH265(videoStream) && _mediaEncoder.SupportsBitStreamFi
 01462                                                            || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStreamFil
 01463                DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus => (IsH265(videoStream) && _mediaEncoder.SupportsBitStream
 01464                                                                 || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStre
 01465                _ => true,
 01466            };
 1467        }
 1468
 1469        public bool IsDoviRemoved(EncodingJobInfo state)
 1470        {
 01471            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01472                                              && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Remove
 1473        }
 1474
 1475        public bool IsHdr10PlusRemoved(EncodingJobInfo state)
 1476        {
 01477            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01478                                                  && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Re
 1479        }
 1480
 1481        public string GetBitStreamArgs(EncodingJobInfo state, MediaStreamType streamType)
 1482        {
 01483            if (state is null)
 1484            {
 01485                return null;
 1486            }
 1487
 01488            var stream = streamType switch
 01489            {
 01490                MediaStreamType.Audio => state.AudioStream,
 01491                MediaStreamType.Video => state.VideoStream,
 01492                _ => state.VideoStream
 01493            };
 1494            // TODO This is auto inserted into the mpegts mux so it might not be needed.
 1495            // https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
 01496            if (IsH264(stream))
 1497            {
 01498                return "-bsf:v h264_mp4toannexb";
 1499            }
 1500
 01501            if (IsAAC(stream))
 1502            {
 1503                // Convert adts header(mpegts) to asc header(mp4).
 01504                return "-bsf:a aac_adtstoasc";
 1505            }
 1506
 01507            if (IsH265(stream))
 1508            {
 01509                var filter = "-bsf:v hevc_mp4toannexb";
 1510
 1511                // The following checks are not complete because the copy would be rejected
 1512                // if the encoder cannot remove required metadata.
 1513                // And if bsf is used, we must already be using copy codec.
 01514                switch (ShouldRemoveDynamicHdrMetadata(state))
 1515                {
 1516                    default:
 1517                    case DynamicHdrMetadataRemovalPlan.None:
 1518                        break;
 1519                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01520                        filter += _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.HevcMetadata
 01521                            ? ",hevc_metadata=remove_dovi=1"
 01522                            : ",dovi_rpu=strip=1";
 01523                        break;
 1524                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01525                        filter += ",hevc_metadata=remove_hdr10plus=1";
 1526                        break;
 1527                }
 1528
 01529                return filter;
 1530            }
 1531
 01532            if (IsAv1(stream))
 1533            {
 01534                switch (ShouldRemoveDynamicHdrMetadata(state))
 1535                {
 1536                    default:
 1537                    case DynamicHdrMetadataRemovalPlan.None:
 01538                        return null;
 1539                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01540                        return _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.Av1MetadataRemo
 01541                            ? "-bsf:v av1_metadata=remove_dovi=1"
 01542                            : "-bsf:v dovi_rpu=strip=1";
 1543                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01544                        return "-bsf:v av1_metadata=remove_hdr10plus=1";
 1545                }
 1546            }
 1547
 01548            return null;
 1549        }
 1550
 1551        public string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceConta
 1552        {
 01553            var bitStreamArgs = string.Empty;
 01554            var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
 1555
 1556            // Apply aac_adtstoasc bitstream filter when media source is in mpegts.
 01557            if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)
 01558                && (string.Equals(mediaSourceContainer, "ts", StringComparison.OrdinalIgnoreCase)
 01559                    || string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase)
 01560                    || string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase)))
 1561            {
 01562                bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Audio);
 01563                bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs;
 1564            }
 1565
 01566            return bitStreamArgs;
 1567        }
 1568
 1569        public static string GetSegmentFileExtension(string segmentContainer)
 1570        {
 01571            if (!string.IsNullOrWhiteSpace(segmentContainer))
 1572            {
 01573                return "." + segmentContainer;
 1574            }
 1575
 01576            return ".ts";
 1577        }
 1578
 1579        private string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
 1580        {
 01581            if (state.OutputVideoBitrate is null)
 1582            {
 01583                return string.Empty;
 1584            }
 1585
 01586            int bitrate = state.OutputVideoBitrate.Value;
 1587
 1588            // Bit rate under 1000k is not allowed in h264_qsv.
 01589            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 1590            {
 01591                bitrate = Math.Max(bitrate, 1000);
 1592            }
 1593
 1594            // Currently use the same buffer size for all non-QSV encoders.
 1595            // Use long arithmetic to prevent int32 overflow for very high bitrate values.
 01596            int bufsize = (int)Math.Min((long)bitrate * 2, int.MaxValue);
 1597
 01598            if (string.Equals(videoCodec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1599            {
 01600                return FormattableString.Invariant($" -b:v {bitrate} -bufsize {bufsize}");
 1601            }
 1602
 01603            if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)
 01604                || string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
 1605            {
 01606                return FormattableString.Invariant($" -maxrate {bitrate} -bufsize {bufsize}");
 1607            }
 1608
 01609            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01610                || string.Equals(videoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 01611                || string.Equals(videoCodec, "av1_qsv", StringComparison.OrdinalIgnoreCase))
 1612            {
 1613                // TODO: probe QSV encoders' capabilities and enable more tuning options
 1614                // See also https://github.com/intel/media-delivery/blob/master/doc/quality.rst
 1615
 1616                // Enable MacroBlock level bitrate control for better subjective visual quality
 01617                var mbbrcOpt = string.Empty;
 01618                if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01619                    || string.Equals(videoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 1620                {
 01621                    mbbrcOpt = " -mbbrc 1";
 1622                }
 1623
 1624                // Some less powerful H.264 HW decoders require strict CPB size
 1625                // So bufsize optimizations should not be applied to them
 01626                int factor = 2;
 01627                var codec = state.ActualOutputVideoCodec;
 01628                var level = state.GetRequestedLevel(codec);
 01629                if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)
 01630                    && double.TryParse(level, CultureInfo.InvariantCulture, out double requestedLevel)
 01631                    && requestedLevel < 51)
 1632                {
 01633                    factor = 1;
 1634                }
 1635
 1636                // Set (maxrate == bitrate + 1) to trigger VBR for better bitrate allocation
 1637                // Set (rc_init_occupancy == 2 * bitrate) and (bufsize == 4 * bitrate) to deal with drastic scene change
 1638                // Use long arithmetic and clamp to int.MaxValue to prevent int32 overflow
 1639                // (e.g. bitrate * 4 wraps to a negative value for bitrates above ~537 million)
 01640                int qsvMaxrate = (int)Math.Min((long)bitrate + 1, int.MaxValue);
 01641                int qsvInitOcc = (int)Math.Min((long)bitrate * 1 * factor, int.MaxValue);
 01642                int qsvBufsize = (int)Math.Min((long)bitrate * 2 * factor, int.MaxValue);
 1643
 01644                return FormattableString.Invariant($"{mbbrcOpt} -b:v {bitrate} -maxrate {qsvMaxrate} -rc_init_occupancy 
 1645            }
 1646
 01647            if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01648                || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01649                || string.Equals(videoCodec, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1650            {
 1651                // Override the too high default qmin 18 in transcoding preset
 01652                return FormattableString.Invariant($" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsiz
 1653            }
 1654
 01655            if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01656                || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01657                || string.Equals(videoCodec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1658            {
 1659                // VBR in i965 driver may result in pixelated output.
 01660                if (_mediaEncoder.IsVaapiDeviceInteli965)
 1661                {
 01662                    return FormattableString.Invariant($" -rc_mode CBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsi
 1663                }
 1664
 01665                return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"
 1666            }
 1667
 01668            if (string.Equals(videoCodec, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 01669                || string.Equals(videoCodec, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase))
 1670            {
 1671                // The `maxrate` and `bufsize` options can potentially lead to performance regression
 1672                // and even encoder hangs, especially when the value is very high.
 01673                return FormattableString.Invariant($" -b:v {bitrate} -qmin -1 -qmax -1");
 1674            }
 1675
 01676            return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
 1677        }
 1678
 1679        private string GetEncoderParam(EncoderPreset? preset, EncoderPreset defaultPreset, EncodingOptions encodingOptio
 1680        {
 01681            var param = string.Empty;
 01682            var encoderPreset = preset ?? defaultPreset;
 01683            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
 1684            {
 01685                var presetString = encoderPreset switch
 01686                {
 01687                    EncoderPreset.auto => EncoderPreset.veryfast.ToString().ToLowerInvariant(),
 01688                    _ => encoderPreset.ToString().ToLowerInvariant()
 01689                };
 1690
 01691                param += " -preset " + presetString;
 1692
 01693                int encodeCrf = encodingOptions.H264Crf;
 01694                if (isLibX265)
 1695                {
 01696                    encodeCrf = encodingOptions.H265Crf;
 1697                }
 1698
 01699                if (encodeCrf >= 0 && encodeCrf <= 51)
 1700                {
 01701                    param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture);
 1702                }
 1703                else
 1704                {
 01705                    string defaultCrf = "23";
 01706                    if (isLibX265)
 1707                    {
 01708                        defaultCrf = "28";
 1709                    }
 1710
 01711                    param += " -crf " + defaultCrf;
 1712                }
 1713            }
 01714            else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1715            {
 1716                // Default to use the recommended preset 10.
 1717                // Omit presets < 5, which are too slow for on the fly encoding.
 1718                // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md
 01719                param += encoderPreset switch
 01720                {
 01721                    EncoderPreset.veryslow => " -preset 5",
 01722                    EncoderPreset.slower => " -preset 6",
 01723                    EncoderPreset.slow => " -preset 7",
 01724                    EncoderPreset.medium => " -preset 8",
 01725                    EncoderPreset.fast => " -preset 9",
 01726                    EncoderPreset.faster => " -preset 10",
 01727                    EncoderPreset.veryfast => " -preset 11",
 01728                    EncoderPreset.superfast => " -preset 12",
 01729                    EncoderPreset.ultrafast => " -preset 13",
 01730                    _ => " -preset 10"
 01731                };
 1732            }
 01733            else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01734                     || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01735                     || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1736            {
 1737                // -compression_level is not reliable on AMD.
 01738                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 1739                {
 01740                    param += encoderPreset switch
 01741                    {
 01742                        EncoderPreset.veryslow => " -compression_level 1",
 01743                        EncoderPreset.slower => " -compression_level 2",
 01744                        EncoderPreset.slow => " -compression_level 3",
 01745                        EncoderPreset.medium => " -compression_level 4",
 01746                        EncoderPreset.fast => " -compression_level 5",
 01747                        EncoderPreset.faster => " -compression_level 6",
 01748                        EncoderPreset.veryfast => " -compression_level 7",
 01749                        EncoderPreset.superfast => " -compression_level 7",
 01750                        EncoderPreset.ultrafast => " -compression_level 7",
 01751                        _ => string.Empty
 01752                    };
 1753                }
 1754            }
 01755            else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv)
 01756                     || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv)
 01757                     || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv)
 1758            {
 01759                EncoderPreset[] valid_presets = [EncoderPreset.veryslow, EncoderPreset.slower, EncoderPreset.slow, Encod
 1760
 01761                param += " -preset " + (valid_presets.Contains(encoderPreset) ? encoderPreset : EncoderPreset.veryfast).
 1762            }
 01763            else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
 01764                        || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_n
 01765                        || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase) // av1 (av1_nven
 01766            )
 1767            {
 01768                param += encoderPreset switch
 01769                {
 01770                        EncoderPreset.veryslow => " -preset p7",
 01771                        EncoderPreset.slower => " -preset p6",
 01772                        EncoderPreset.slow => " -preset p5",
 01773                        EncoderPreset.medium => " -preset p4",
 01774                        EncoderPreset.fast => " -preset p3",
 01775                        EncoderPreset.faster => " -preset p2",
 01776                        _ => " -preset p1"
 01777                };
 1778            }
 01779            else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf)
 01780                        || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf
 01781                        || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase) // av1 (av1_amf)
 01782            )
 1783            {
 01784                param += encoderPreset switch
 01785                {
 01786                        EncoderPreset.veryslow => " -quality quality",
 01787                        EncoderPreset.slower => " -quality quality",
 01788                        EncoderPreset.slow => " -quality quality",
 01789                        EncoderPreset.medium => " -quality balanced",
 01790                        _ => " -quality speed"
 01791                };
 1792
 01793                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01794                    || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1795                {
 01796                    param += " -header_insertion_mode gop";
 1797                }
 1798
 01799                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
 1800                {
 01801                    param += " -gops_per_idr 1";
 1802                }
 1803            }
 01804            else if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase) // h264 (h264_
 01805                        || string.Equals(videoEncoder, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase) // hevc 
 01806            )
 1807            {
 01808                param += encoderPreset switch
 01809                {
 01810                        EncoderPreset.veryslow => " -prio_speed 0",
 01811                        EncoderPreset.slower => " -prio_speed 0",
 01812                        EncoderPreset.slow => " -prio_speed 0",
 01813                        EncoderPreset.medium => " -prio_speed 0",
 01814                        _ => " -prio_speed 1"
 01815                };
 1816            }
 1817
 01818            return param;
 1819        }
 1820
 1821        public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
 1822        {
 01823            if (!double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel))
 1824            {
 01825                return null;
 1826            }
 1827
 01828            if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase))
 1829            {
 1830                // Transcode to level 5.3 (15) and lower for maximum compatibility.
 1831                // https://en.wikipedia.org/wiki/AV1#Levels
 01832                if (requestLevel < 0 || requestLevel >= 15)
 1833                {
 01834                    return "15";
 1835                }
 1836            }
 01837            else if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 01838                     || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
 1839            {
 1840                // Transcode to level 5.0 and lower for maximum compatibility.
 1841                // Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it.
 1842                // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
 1843                // MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880.
 01844                if (requestLevel < 0 || requestLevel >= 150)
 1845                {
 01846                    return "150";
 1847                }
 1848            }
 01849            else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
 1850            {
 1851                // Transcode to level 5.1 and lower for maximum compatibility.
 1852                // h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
 1853                // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
 01854                if (requestLevel < 0 || requestLevel >= 51)
 1855                {
 01856                    return "51";
 1857                }
 1858            }
 1859
 01860            return level;
 1861        }
 1862
 1863        /// <summary>
 1864        /// Gets the text subtitle param.
 1865        /// </summary>
 1866        /// <param name="state">The state.</param>
 1867        /// <param name="enableAlpha">Enable alpha processing.</param>
 1868        /// <param name="enableSub2video">Enable sub2video mode.</param>
 1869        /// <returns>System.String.</returns>
 1870        public string GetTextSubtitlesFilter(EncodingJobInfo state, bool enableAlpha, bool enableSub2video)
 1871        {
 01872            var seconds = Math.Round(TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds);
 1873
 1874            // hls always copies timestamps
 01875            var setPtsParam = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive
 01876                ? string.Empty
 01877                : string.Format(CultureInfo.InvariantCulture, ",setpts=PTS -{0}/TB", seconds);
 1878
 01879            var alphaParam = enableAlpha ? ":alpha=1" : string.Empty;
 01880            var sub2videoParam = enableSub2video ? ":sub2video=1" : string.Empty;
 1881
 01882            var fontPath = _pathManager.GetAttachmentFolderPath(state.MediaSource.Id);
 01883            var fontParam = string.Format(
 01884                CultureInfo.InvariantCulture,
 01885                ":fontsdir='{0}'",
 01886                _mediaEncoder.EscapeSubtitleFilterPath(fontPath));
 1887
 01888            if (state.SubtitleStream.IsExternal)
 1889            {
 01890                var charsetParam = string.Empty;
 1891
 01892                if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
 1893                {
 01894                    var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
 01895                            state.SubtitleStream,
 01896                            state.SubtitleStream.Language,
 01897                            state.MediaSource,
 01898                            CancellationToken.None).GetAwaiter().GetResult();
 1899
 01900                    if (!string.IsNullOrEmpty(charenc))
 1901                    {
 01902                        charsetParam = ":charenc=" + charenc;
 1903                    }
 1904                }
 1905
 01906                return string.Format(
 01907                    CultureInfo.InvariantCulture,
 01908                    "subtitles=f='{0}'{1}{2}{3}{4}{5}",
 01909                    _mediaEncoder.EscapeSubtitleFilterPath(state.SubtitleStream.Path),
 01910                    charsetParam,
 01911                    alphaParam,
 01912                    sub2videoParam,
 01913                    fontParam,
 01914                    setPtsParam);
 1915            }
 1916
 01917            var subtitlePath = _subtitleEncoder.GetSubtitleFilePath(
 01918                    state.SubtitleStream,
 01919                    state.MediaSource,
 01920                    CancellationToken.None).GetAwaiter().GetResult();
 1921
 01922            return string.Format(
 01923                CultureInfo.InvariantCulture,
 01924                "subtitles=f='{0}'{1}{2}{3}{4}",
 01925                _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
 01926                alphaParam,
 01927                sub2videoParam,
 01928                fontParam,
 01929                setPtsParam);
 1930        }
 1931
 1932        public double? GetFramerateParam(EncodingJobInfo state)
 1933        {
 01934            var request = state.BaseRequest;
 1935
 01936            if (request.Framerate.HasValue)
 1937            {
 01938                return request.Framerate.Value;
 1939            }
 1940
 01941            var maxrate = request.MaxFramerate;
 1942
 01943            if (maxrate.HasValue && state.VideoStream is not null)
 1944            {
 01945                var contentRate = state.VideoStream.ReferenceFrameRate;
 1946
 01947                if (contentRate.HasValue && contentRate.Value > maxrate.Value)
 1948                {
 01949                    return maxrate;
 1950                }
 1951            }
 1952
 01953            return null;
 1954        }
 1955
 1956        public string GetHlsVideoKeyFrameArguments(
 1957            EncodingJobInfo state,
 1958            string codec,
 1959            int segmentLength,
 1960            bool isEventPlaylist,
 1961            int? startNumber)
 1962        {
 01963            var args = string.Empty;
 01964            var gopArg = string.Empty;
 1965
 01966            var keyFrameArg = string.Format(
 01967                CultureInfo.InvariantCulture,
 01968                " -force_key_frames:0 \"expr:gte(t,n_forced*{0})\"",
 01969                segmentLength);
 1970
 01971            var framerate = state.VideoStream?.RealFrameRate;
 01972            if (framerate.HasValue)
 1973            {
 1974                // This is to make sure keyframe interval is limited to our segment,
 1975                // as forcing keyframes is not enough.
 1976                // Example: we encoded half of desired length, then codec detected
 1977                // scene cut and inserted a keyframe; next forced keyframe would
 1978                // be created outside of segment, which breaks seeking.
 01979                gopArg = string.Format(
 01980                    CultureInfo.InvariantCulture,
 01981                    " -g:v:0 {0} -keyint_min:v:0 {0}",
 01982                    Math.Ceiling(segmentLength * framerate.Value));
 1983            }
 1984
 1985            // Unable to force key frames using these encoders, set key frames by GOP.
 01986            if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01987                || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 01988                || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01989                || string.Equals(codec, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 01990                || string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 01991                || string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 01992                || string.Equals(codec, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase)
 01993                || string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 01994                || string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 01995                || string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase)
 01996                || string.Equals(codec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1997            {
 01998                args += gopArg;
 1999            }
 02000            else if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase)
 02001                     || string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)
 02002                     || string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02003                     || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02004                     || string.Equals(codec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 2005            {
 02006                args += keyFrameArg;
 2007
 2008                // prevent the libx264 from post processing to break the set keyframe.
 02009                if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase))
 2010                {
 02011                    args += " -sc_threshold:v:0 0";
 2012                }
 2013            }
 2014            else
 2015            {
 02016                args += keyFrameArg + gopArg;
 2017            }
 2018
 2019            // global_header produced by AMD HEVC VA-API encoder causes non-playable fMP4 on iOS
 02020            if (string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02021                && _mediaEncoder.IsVaapiDeviceAmd)
 2022            {
 02023                args += " -flags:v -global_header";
 2024            }
 2025
 02026            return args;
 2027        }
 2028
 2029        /// <summary>
 2030        /// Gets the video bitrate to specify on the command line.
 2031        /// </summary>
 2032        /// <param name="state">Encoding state.</param>
 2033        /// <param name="videoEncoder">Video encoder to use.</param>
 2034        /// <param name="encodingOptions">Encoding options.</param>
 2035        /// <param name="defaultPreset">Default present to use for encoding.</param>
 2036        /// <returns>Video bitrate.</returns>
 2037        public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, 
 2038        {
 02039            var param = string.Empty;
 2040
 2041            // Tutorials: Enable Intel GuC / HuC firmware loading for Low Power Encoding.
 2042            // https://01.org/group/43/downloads/firmware
 2043            // https://wiki.archlinux.org/title/intel_graphics#Enable_GuC_/_HuC_firmware_loading
 2044            // Intel Low Power Encoding can save unnecessary CPU-GPU synchronization,
 2045            // which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
 02046            var intelLowPowerHwEncoding = false;
 2047
 2048            // Workaround for linux 5.18 to 6.1.3 i915 hang at cost of performance.
 2049            // https://github.com/intel/media-driver/issues/1456
 02050            var enableWaFori915Hang = false;
 2051
 02052            var hardwareAccelerationType = encodingOptions.HardwareAccelerationType;
 2053
 02054            if (hardwareAccelerationType == HardwareAccelerationType.vaapi)
 2055            {
 02056                var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
 2057
 02058                if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
 2059                {
 02060                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder && isIntelVaapiDriver;
 2061                }
 02062                else if (string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
 2063                {
 02064                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder && isIntelVaapiDriver;
 2065                }
 2066            }
 02067            else if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 2068            {
 02069                if (OperatingSystem.IsLinux())
 2070                {
 02071                    var ver = Environment.OSVersion.Version;
 02072                    var isFixedKernel60 = ver.Major == 6 && ver.Minor == 0 && ver >= _minFixedKernel60i915Hang;
 02073                    var isUnaffectedKernel = ver < _minKerneli915Hang || ver > _maxKerneli915Hang;
 2074
 02075                    if (!(isUnaffectedKernel || isFixedKernel60))
 2076                    {
 02077                        var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
 02078                        var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
 02079                                             || vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 02080                        var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
 02081                            && IsVaapiSupported(state)
 02082                            && IsOpenclFullSupported()
 02083                            && !IsIntelVppTonemapAvailable(state, encodingOptions)
 02084                            && IsHwTonemapAvailable(state, encodingOptions);
 2085
 02086                        enableWaFori915Hang = isIntelDecoder && doOclTonemap;
 2087                    }
 2088                }
 2089
 02090                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 2091                {
 02092                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder;
 2093                }
 02094                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2095                {
 02096                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder;
 2097                }
 2098                else
 2099                {
 02100                    enableWaFori915Hang = false;
 2101                }
 2102            }
 2103
 02104            if (intelLowPowerHwEncoding)
 2105            {
 02106                param += " -low_power 1";
 2107            }
 2108
 02109            if (enableWaFori915Hang)
 2110            {
 02111                param += " -async_depth 1";
 2112            }
 2113
 02114            var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
 02115            var encodingPreset = encodingOptions.EncoderPreset;
 2116
 02117            param += GetEncoderParam(encodingPreset, defaultPreset, encodingOptions, videoEncoder, isLibX265);
 02118            param += GetVideoBitrateParam(state, videoEncoder);
 2119
 02120            var framerate = GetFramerateParam(state);
 02121            if (framerate.HasValue)
 2122            {
 02123                param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(CultureInfo.Inv
 2124            }
 2125
 02126            var targetVideoCodec = state.ActualOutputVideoCodec;
 02127            if (string.Equals(targetVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
 02128                || string.Equals(targetVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
 2129            {
 02130                targetVideoCodec = "hevc";
 2131            }
 2132
 02133            var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
 02134            profile = WhiteSpaceRegex().Replace(profile, string.Empty).ToLowerInvariant();
 2135
 02136            var videoProfiles = Array.Empty<string>();
 02137            if (string.Equals("h264", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2138            {
 02139                videoProfiles = _videoProfilesH264;
 2140            }
 02141            else if (string.Equals("hevc", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2142            {
 02143                videoProfiles = _videoProfilesH265;
 2144            }
 02145            else if (string.Equals("av1", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2146            {
 02147                videoProfiles = _videoProfilesAv1;
 2148            }
 2149
 02150            if (!videoProfiles.Contains(profile, StringComparison.OrdinalIgnoreCase))
 2151            {
 02152                profile = string.Empty;
 2153            }
 2154
 2155            // We only transcode to HEVC 8-bit for now, force Main Profile.
 02156            if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)
 02157                || profile.Contains("mainstill", StringComparison.OrdinalIgnoreCase))
 2158            {
 02159                profile = "main";
 2160            }
 2161
 2162            // Extended Profile is not supported by any known h264 encoders, force Main Profile.
 02163            if (profile.Contains("extended", StringComparison.OrdinalIgnoreCase))
 2164            {
 02165                profile = "main";
 2166            }
 2167
 2168            // Only libx264 support encoding H264 High 10 Profile, otherwise force High Profile.
 02169            if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02170                && profile.Contains("high10", StringComparison.OrdinalIgnoreCase))
 2171            {
 02172                profile = "high";
 2173            }
 2174
 2175            // We only need Main profile of AV1 encoders.
 02176            if (videoEncoder.Contains("av1", StringComparison.OrdinalIgnoreCase)
 02177                && (profile.Contains("high", StringComparison.OrdinalIgnoreCase)
 02178                    || profile.Contains("professional", StringComparison.OrdinalIgnoreCase)))
 2179            {
 02180                profile = "main";
 2181            }
 2182
 2183            // h264_vaapi does not support Baseline profile, force Constrained Baseline in this case,
 2184            // which is compatible (and ugly).
 02185            if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02186                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2187            {
 02188                profile = "constrained_baseline";
 2189            }
 2190
 2191            // libx264, h264_{qsv,nvenc,rkmpp} does not support Constrained Baseline profile, force Baseline in this cas
 02192            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02193                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02194                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02195                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02196                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2197            {
 02198                profile = "baseline";
 2199            }
 2200
 2201            // libx264, h264_{qsv,nvenc,vaapi,rkmpp} does not support Constrained High profile, force High in this case.
 02202            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02203                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02204                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02205                 || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02206                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02207                && profile.Contains("high", StringComparison.OrdinalIgnoreCase))
 2208            {
 02209                profile = "high";
 2210            }
 2211
 02212            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02213                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2214            {
 02215                profile = "constrained_baseline";
 2216            }
 2217
 02218            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02219                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2220            {
 02221                profile = "constrained_high";
 2222            }
 2223
 02224            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02225                && profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase))
 2226            {
 02227                profile = "constrained_baseline";
 2228            }
 2229
 02230            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02231                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2232            {
 02233                profile = "constrained_high";
 2234            }
 2235
 02236            if (!string.IsNullOrEmpty(profile))
 2237            {
 2238                // Currently there's no profile option in av1_nvenc encoder
 02239                if (!(string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 02240                      || string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)))
 2241                {
 02242                    param += " -profile:v:0 " + profile;
 2243                }
 2244            }
 2245
 02246            var level = NormalizeTranscodingLevel(state, state.GetRequestedLevel(targetVideoCodec));
 2247
 02248            if (!string.IsNullOrEmpty(level))
 2249            {
 2250                // libx264, QSV, AMF can adjust the given level to match the output.
 02251                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02252                    || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2253                {
 02254                    param += " -level " + level;
 2255                }
 02256                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2257                {
 2258                    // hevc_qsv use -level 51 instead of -level 153.
 02259                    if (double.TryParse(level, CultureInfo.InvariantCulture, out double hevcLevel))
 2260                    {
 02261                        param += " -level " + (hevcLevel / 3);
 2262                    }
 2263                }
 02264                else if (string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 02265                         || string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 2266                {
 2267                    // libsvtav1 and av1_qsv use -level 60 instead of -level 16
 2268                    // https://aomedia.org/av1/specification/annex-a/
 02269                    if (int.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out int av1Level))
 2270                    {
 02271                        var x = 2 + (av1Level >> 2);
 02272                        var y = av1Level & 3;
 02273                        var res = (x * 10) + y;
 02274                        param += " -level " + res;
 2275                    }
 2276                }
 02277                else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02278                         || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 02279                         || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 2280                {
 02281                    param += " -level " + level;
 2282                }
 02283                else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02284                         || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 02285                         || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase))
 2286                {
 2287                    // level option may cause NVENC to fail.
 2288                    // NVENC cannot adjust the given level, just throw an error.
 2289                }
 02290                else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02291                         || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02292                         || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 2293                {
 2294                    // level option may cause corrupted frames on AMD VAAPI.
 02295                    if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 2296                    {
 02297                        param += " -level " + level;
 2298                    }
 2299                }
 02300                else if (string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 02301                         || string.Equals(videoEncoder, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase))
 2302                {
 02303                    param += " -level " + level;
 2304                }
 02305                else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2306                {
 02307                    param += " -level " + level;
 2308                }
 2309            }
 2310
 02311            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2312            {
 02313                param += " -x264opts:0 subme=0:me_range=16:rc_lookahead=10:me=hex:open_gop=0";
 2314            }
 2315
 02316            if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2317            {
 2318                // libx265 only accept level option in -x265-params.
 2319                // level option may cause libx265 to fail.
 2320                // libx265 cannot adjust the given level, just throw an error.
 02321                param += " -x265-params:0 no-scenecut=1:no-open-gop=1:no-info=1";
 2322
 02323                if (encodingOptions.EncoderPreset < EncoderPreset.ultrafast)
 2324                {
 2325                    // The following params are slower than the ultrafast preset, don't use when ultrafast is selected.
 02326                    param += ":subme=3:merange=25:rc-lookahead=10:me=star:ctu=32:max-tu-size=32:min-cu-size=16:rskip=2:r
 2327                }
 2328            }
 2329
 02330            if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)
 02331                && _mediaEncoder.EncoderVersion >= _minFFmpegSvtAv1Params)
 2332            {
 02333                param += " -svtav1-params:0 rc=1:tune=0:film-grain=0:enable-overlays=1:enable-tf=0";
 2334            }
 2335
 2336            /* Access unit too large: 8192 < 20880 error */
 02337            if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) ||
 02338                 string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) &&
 02339                 _mediaEncoder.EncoderVersion >= _minFFmpegVaapiH26xEncA53CcSei)
 2340            {
 02341                param += " -sei -a53_cc";
 2342            }
 2343
 02344            return param;
 2345        }
 2346
 2347        public bool CanStreamCopyVideo(EncodingJobInfo state, MediaStream videoStream)
 2348        {
 02349            var request = state.BaseRequest;
 2350
 02351            if (!request.AllowVideoStreamCopy)
 2352            {
 02353                return false;
 2354            }
 2355
 02356            if (videoStream.IsInterlaced
 02357                && state.DeInterlace(videoStream.Codec, false))
 2358            {
 02359                return false;
 2360            }
 2361
 02362            if (videoStream.IsAnamorphic ?? false)
 2363            {
 02364                if (request.RequireNonAnamorphic)
 2365                {
 02366                    return false;
 2367                }
 2368            }
 2369
 2370            // Can't stream copy if we're burning in subtitles
 02371            if (request.SubtitleStreamIndex.HasValue
 02372                && request.SubtitleStreamIndex.Value >= 0
 02373                && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
 2374            {
 02375                return false;
 2376            }
 2377
 02378            if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 02379                && videoStream.IsAVC.HasValue
 02380                && !videoStream.IsAVC.Value
 02381                && request.RequireAvc)
 2382            {
 02383                return false;
 2384            }
 2385
 2386            // Source and target codecs must match
 02387            if (string.IsNullOrEmpty(videoStream.Codec)
 02388                || (state.SupportedVideoCodecs.Length != 0
 02389                    && !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparison.OrdinalIgnoreCase)))
 2390            {
 02391                return false;
 2392            }
 2393
 02394            var requestedProfiles = state.GetRequestedProfiles(videoStream.Codec);
 2395
 2396            // If client is requesting a specific video profile, it must match the source
 02397            if (requestedProfiles.Length > 0)
 2398            {
 02399                if (string.IsNullOrEmpty(videoStream.Profile))
 2400                {
 2401                    // return false;
 2402                }
 2403
 02404                var requestedProfile = requestedProfiles[0];
 2405                // strip spaces because they may be stripped out on the query string as well
 02406                if (!string.IsNullOrEmpty(videoStream.Profile)
 02407                    && !requestedProfiles.Contains(videoStream.Profile.Replace(" ", string.Empty, StringComparison.Ordin
 2408                {
 02409                    var currentScore = GetVideoProfileScore(videoStream.Codec, videoStream.Profile);
 02410                    var requestedScore = GetVideoProfileScore(videoStream.Codec, requestedProfile);
 2411
 02412                    if (currentScore == -1 || currentScore > requestedScore)
 2413                    {
 02414                        return false;
 2415                    }
 2416                }
 2417            }
 2418
 02419            var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
 02420            if (requestedRangeTypes.Length > 0)
 2421            {
 02422                if (videoStream.VideoRangeType == VideoRangeType.Unknown)
 2423                {
 02424                    return false;
 2425                }
 2426
 2427                // DOVIWithHDR10 should be compatible with HDR10 supporting players. Same goes with HLG and of course SD
 02428                var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ord
 02429                var requestHasHLG = requestedRangeTypes.Contains(VideoRangeType.HLG.ToString(), StringComparison.Ordinal
 02430                var requestHasSDR = requestedRangeTypes.Contains(VideoRangeType.SDR.ToString(), StringComparison.Ordinal
 02431                var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.Ordin
 2432
 2433                // If SDR is the only supported range, we should not copy any of the HDR streams.
 2434                // All the following copy check assumes at least one HDR format is supported.
 02435                if (requestedRangeTypes.Length == 1 && requestHasSDR && videoStream.VideoRangeType != VideoRangeType.SDR
 2436                {
 02437                    return false;
 2438                }
 2439
 2440                // If the client does not support DOVI and the video stream is DOVI without fallback, we should not copy
 02441                if (!requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVI)
 2442                {
 02443                    return false;
 2444                }
 2445
 02446                if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreC
 02447                     && !((requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10)
 02448                            || (requestHasHLG && videoStream.VideoRangeType == VideoRangeType.DOVIWithHLG)
 02449                            || (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)
 02450                            || (requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.HDR10Plus)))
 2451                {
 2452                    // If the video stream is in HDR10+ or a static HDR format, don't allow copy if the client does not 
 02453                    if (videoStream.VideoRangeType is VideoRangeType.HDR10Plus or VideoRangeType.HDR10 or VideoRangeType
 2454                    {
 02455                        return false;
 2456                    }
 2457
 2458                    // Check complicated cases where we need to remove dynamic metadata
 2459                    // Conservatively refuse to copy if the encoder can't remove dynamic metadata,
 2460                    // but a removal is required for compatability reasons.
 02461                    var dynamicHdrMetadataRemovalPlan = ShouldRemoveDynamicHdrMetadata(state);
 02462                    if (!CanEncoderRemoveDynamicHdrMetadata(dynamicHdrMetadataRemovalPlan, videoStream))
 2463                    {
 02464                        return false;
 2465                    }
 2466                }
 2467            }
 2468
 2469            // Video width must fall within requested value
 02470            if (request.MaxWidth.HasValue
 02471                && (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
 2472            {
 02473                return false;
 2474            }
 2475
 2476            // Video height must fall within requested value
 02477            if (request.MaxHeight.HasValue
 02478                && (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value))
 2479            {
 02480                return false;
 2481            }
 2482
 2483            // Video framerate must fall within requested value
 02484            var requestedFramerate = request.MaxFramerate ?? request.Framerate;
 02485            if (requestedFramerate.HasValue)
 2486            {
 02487                var videoFrameRate = videoStream.ReferenceFrameRate;
 2488
 2489                // Add a little tolerance to the framerate check because some videos might record a framerate
 2490                // that is slightly greater than the intended framerate, but the device can still play it correctly.
 2491                // 0.05 fps tolerance should be safe enough.
 02492                if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value + 0.05f)
 2493                {
 02494                    return false;
 2495                }
 2496            }
 2497
 2498            // Video bitrate must fall within requested value
 02499            if (request.VideoBitRate.HasValue
 02500                && (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value))
 2501            {
 2502                // For LiveTV that has no bitrate, let's try copy if other conditions are met
 02503                if (string.IsNullOrWhiteSpace(request.LiveStreamId) || videoStream.BitRate.HasValue)
 2504                {
 02505                    return false;
 2506                }
 2507            }
 2508
 02509            var maxBitDepth = state.GetRequestedVideoBitDepth(videoStream.Codec);
 02510            if (maxBitDepth.HasValue)
 2511            {
 02512                if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > maxBitDepth.Value)
 2513                {
 02514                    return false;
 2515                }
 2516            }
 2517
 02518            var maxRefFrames = state.GetRequestedMaxRefFrames(videoStream.Codec);
 02519            if (maxRefFrames.HasValue
 02520                && videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > maxRefFrames.Value)
 2521            {
 02522                return false;
 2523            }
 2524
 2525            // If a specific level was requested, the source must match or be less than
 02526            var level = state.GetRequestedLevel(videoStream.Codec);
 02527            if (double.TryParse(level, CultureInfo.InvariantCulture, out var requestLevel))
 2528            {
 02529                if (!videoStream.Level.HasValue)
 2530                {
 2531                    // return false;
 2532                }
 2533
 02534                if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
 2535                {
 02536                    return false;
 2537                }
 2538            }
 2539
 02540            if (string.Equals(state.InputContainer, "avi", StringComparison.OrdinalIgnoreCase)
 02541                && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)
 02542                && !(videoStream.IsAVC ?? false))
 2543            {
 2544                // see Coach S01E01 - Kelly and the Professor(0).avi
 02545                return false;
 2546            }
 2547
 02548            return true;
 2549        }
 2550
 2551        public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, IEnumerable<string> supportedAudi
 2552        {
 02553            var request = state.BaseRequest;
 2554
 02555            if (!request.AllowAudioStreamCopy)
 2556            {
 02557                return false;
 2558            }
 2559
 02560            var maxBitDepth = state.GetRequestedAudioBitDepth(audioStream.Codec);
 02561            if (maxBitDepth.HasValue
 02562                && audioStream.BitDepth.HasValue
 02563                && audioStream.BitDepth.Value > maxBitDepth.Value)
 2564            {
 02565                return false;
 2566            }
 2567
 2568            // Source and target codecs must match
 02569            if (string.IsNullOrEmpty(audioStream.Codec)
 02570                || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparison.OrdinalIgnoreCase))
 2571            {
 02572                return false;
 2573            }
 2574
 2575            // Channels must fall within requested value
 02576            var channels = state.GetRequestedAudioChannels(audioStream.Codec);
 02577            if (channels.HasValue)
 2578            {
 02579                if (!audioStream.Channels.HasValue || audioStream.Channels.Value <= 0)
 2580                {
 02581                    return false;
 2582                }
 2583
 02584                if (audioStream.Channels.Value > channels.Value)
 2585                {
 02586                    return false;
 2587                }
 2588            }
 2589
 2590            // Sample rate must fall within requested value
 02591            if (request.AudioSampleRate.HasValue)
 2592            {
 02593                if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value <= 0)
 2594                {
 02595                    return false;
 2596                }
 2597
 02598                if (audioStream.SampleRate.Value > request.AudioSampleRate.Value)
 2599                {
 02600                    return false;
 2601                }
 2602            }
 2603
 2604            // Audio bitrate must fall within requested value
 02605            if (request.AudioBitRate.HasValue
 02606                && audioStream.BitRate.HasValue
 02607                && audioStream.BitRate.Value > request.AudioBitRate.Value)
 2608            {
 02609                return false;
 2610            }
 2611
 02612            return request.EnableAutoStreamCopy;
 2613        }
 2614
 2615        public int GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideo
 2616        {
 02617            var bitrate = request.VideoBitRate;
 2618
 02619            if (videoStream is not null)
 2620            {
 02621                var isUpscaling = request.Height.HasValue
 02622                    && videoStream.Height.HasValue
 02623                    && request.Height.Value > videoStream.Height.Value
 02624                    && request.Width.HasValue
 02625                    && videoStream.Width.HasValue
 02626                    && request.Width.Value > videoStream.Width.Value;
 2627
 2628                // Don't allow bitrate increases unless upscaling
 02629                if (!isUpscaling && bitrate.HasValue && videoStream.BitRate.HasValue)
 2630                {
 02631                    bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value);
 2632                }
 2633
 02634                if (bitrate.HasValue)
 2635                {
 02636                    var inputVideoCodec = videoStream.Codec;
 02637                    bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
 2638
 2639                    // If a max bitrate was requested, don't let the scaled bitrate exceed it
 02640                    if (request.VideoBitRate.HasValue)
 2641                    {
 02642                        bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
 2643                    }
 2644                }
 2645            }
 2646
 2647            // Cap the max target bitrate to 400 Mbps.
 2648            // No consumer or professional hardware transcode target exceeds this value
 2649            // (Intel QSV tops out at ~300 Mbps for H.264; HEVC High Tier Level 5.x is ~240 Mbps).
 2650            // Without this cap, plugin-provided MPEG-TS streams with no usable bitrate metadata
 2651            // can produce unreasonably large -bufsize/-maxrate values for the encoder.
 2652            // Note: the existing FallbackMaxStreamingBitrate mechanism (default 30 Mbps) only
 2653            // applies when a LiveStreamId is set (M3U/HDHR sources). Plugin streams and other
 2654            // sources that bypass the LiveTV pipeline are not covered by it.
 2655            const int MaxSaneBitrate = 400_000_000; // 400 Mbps
 02656            return Math.Min(bitrate ?? 0, MaxSaneBitrate);
 2657        }
 2658
 2659        private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
 2660        {
 2661            // these values were chosen from testing to improve low bitrate streams
 02662            if (sourceBitrate <= 2000000)
 2663            {
 02664                sourceBitrate = Convert.ToInt32(sourceBitrate * 2.5);
 2665            }
 02666            else if (sourceBitrate <= 3000000)
 2667            {
 02668                sourceBitrate *= 2;
 2669            }
 2670
 02671            var bitrate = Math.Min(sourceBitrate, requestedBitrate);
 2672
 02673            return bitrate;
 2674        }
 2675
 2676        private static double GetVideoBitrateScaleFactor(string codec)
 2677        {
 2678            // hevc & vp9 - 40% more efficient than h.264
 02679            if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 02680                || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
 02681                || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
 2682            {
 02683                return .6;
 2684            }
 2685
 2686            // av1 - 50% more efficient than h.264
 02687            if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 2688            {
 02689                return .5;
 2690            }
 2691
 02692            return 1;
 2693        }
 2694
 2695        public static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
 2696        {
 02697            var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
 02698            var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
 2699
 2700            // Don't scale the real bitrate lower than the requested bitrate
 02701            var scaleFactor = Math.Max(outputScaleFactor / inputScaleFactor, 1);
 2702
 02703            if (bitrate <= 500000)
 2704            {
 02705                scaleFactor = Math.Max(scaleFactor, 4);
 2706            }
 02707            else if (bitrate <= 1000000)
 2708            {
 02709                scaleFactor = Math.Max(scaleFactor, 3);
 2710            }
 02711            else if (bitrate <= 2000000)
 2712            {
 02713                scaleFactor = Math.Max(scaleFactor, 2.5);
 2714            }
 02715            else if (bitrate <= 3000000)
 2716            {
 02717                scaleFactor = Math.Max(scaleFactor, 2);
 2718            }
 02719            else if (bitrate >= 30000000)
 2720            {
 2721                // Don't scale beyond 30Mbps, it is hardly visually noticeable for most codecs with our prefer speed enc
 2722                // and will cause extremely high bitrate to be used for av1->h264 transcoding that will overload clients
 02723                scaleFactor = 1;
 2724            }
 2725
 02726            return Convert.ToInt32(scaleFactor * bitrate);
 2727        }
 2728
 2729        public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChanne
 2730        {
 02731            return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels);
 2732        }
 2733
 2734        public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudio
 2735        {
 02736            if (audioStream is null)
 2737            {
 02738                return null;
 2739            }
 2740
 02741            var inputChannels = audioStream.Channels ?? 0;
 02742            var outputChannels = outputAudioChannels ?? 0;
 02743            var bitrate = audioBitRate ?? int.MaxValue;
 2744
 02745            if (string.IsNullOrEmpty(audioCodec)
 02746                || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
 02747                || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
 02748                || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
 02749                || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
 02750                || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
 02751                || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
 2752            {
 02753                return (inputChannels, outputChannels) switch
 02754                {
 02755                    (>= 6, >= 6 or 0) => Math.Min(640000, bitrate),
 02756                    (> 0, > 0) => Math.Min(outputChannels * 128000, bitrate),
 02757                    (> 0, _) => Math.Min(inputChannels * 128000, bitrate),
 02758                    (_, _) => Math.Min(384000, bitrate)
 02759                };
 2760            }
 2761
 02762            if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
 02763                || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
 2764            {
 02765                return (inputChannels, outputChannels) switch
 02766                {
 02767                    (>= 6, >= 6 or 0) => Math.Min(768000, bitrate),
 02768                    (> 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
 02769                    (> 0, _) => Math.Min(inputChannels * 136000, bitrate),
 02770                    (_, _) => Math.Min(672000, bitrate)
 02771                };
 2772            }
 2773
 2774            // Empty bitrate area is not allow on iOS
 2775            // Default audio bitrate to 128K per channel if we don't have codec specific defaults
 2776            // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
 02777            return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
 2778        }
 2779
 2780        public string GetAudioVbrModeParam(string encoder, int bitrate, int channels)
 2781        {
 02782            var bitratePerChannel = bitrate / Math.Max(channels, 1);
 02783            if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
 2784            {
 02785                return " -vbr:a " + bitratePerChannel switch
 02786                {
 02787                    < 32000 => "1",
 02788                    < 48000 => "2",
 02789                    < 64000 => "3",
 02790                    < 96000 => "4",
 02791                    _ => "5"
 02792                };
 2793            }
 2794
 02795            if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase))
 2796            {
 2797                // lame's VBR is only good for a certain bitrate range
 2798                // For very low and very high bitrate, use abr mode
 02799                if (bitratePerChannel is < 122500 and > 48000)
 2800                {
 02801                    return " -qscale:a " + bitratePerChannel switch
 02802                    {
 02803                        < 64000 => "6",
 02804                        < 88000 => "4",
 02805                        < 112000 => "2",
 02806                        _ => "0"
 02807                    };
 2808                }
 2809
 02810                return " -abr:a 1" + " -b:a " + bitrate;
 2811            }
 2812
 02813            if (string.Equals(encoder, "aac_at", StringComparison.OrdinalIgnoreCase))
 2814            {
 2815                // aac_at's CVBR mode
 02816                return " -aac_at_mode:a 2" + " -b:a " + bitrate;
 2817            }
 2818
 02819            if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase))
 2820            {
 02821                return " -qscale:a " + bitratePerChannel switch
 02822                {
 02823                    < 40000 => "0",
 02824                    < 56000 => "2",
 02825                    < 80000 => "4",
 02826                    < 112000 => "6",
 02827                    _ => "8"
 02828                };
 2829            }
 2830
 02831            return null;
 2832        }
 2833
 2834        public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
 2835        {
 02836            var channels = state.OutputAudioChannels;
 2837
 02838            var filters = new List<string>();
 2839
 02840            if (channels is 2 && state.AudioStream?.Channels is > 2)
 2841            {
 02842                var hasDownMixFilter = DownMixAlgorithmsHelper.AlgorithmFilterStrings.TryGetValue((encodingOptions.DownM
 02843                if (hasDownMixFilter)
 2844                {
 02845                    filters.Add(downMixFilterString);
 2846                }
 2847
 02848                if (!encodingOptions.DownMixAudioBoost.Equals(1))
 2849                {
 02850                    filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
 2851                }
 2852            }
 2853
 02854            var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
 02855            if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && ShouldEncodeSubtitle(st
 2856            {
 02857                var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds;
 2858
 02859                filters.Add(
 02860                    string.Format(
 02861                        CultureInfo.InvariantCulture,
 02862                        "asetpts=PTS-{0}/TB",
 02863                        Math.Round(seconds)));
 2864            }
 2865
 02866            if (filters.Count > 0)
 2867            {
 02868                return " -af \"" + string.Join(',', filters) + "\"";
 2869            }
 2870
 02871            return string.Empty;
 2872        }
 2873
 2874        /// <summary>
 2875        /// Gets the number of audio channels to specify on the command line.
 2876        /// </summary>
 2877        /// <param name="state">The state.</param>
 2878        /// <param name="audioStream">The audio stream.</param>
 2879        /// <param name="outputAudioCodec">The output audio codec.</param>
 2880        /// <returns>System.Nullable{System.Int32}.</returns>
 2881        public int? GetNumAudioChannelsParam(EncodingJobInfo state, MediaStream audioStream, string outputAudioCodec)
 2882        {
 02883            if (audioStream is null)
 2884            {
 02885                return null;
 2886            }
 2887
 02888            var request = state.BaseRequest;
 2889
 02890            var codec = outputAudioCodec ?? string.Empty;
 2891
 02892            int? resultChannels = state.GetRequestedAudioChannels(codec);
 2893
 02894            var inputChannels = audioStream.Channels;
 2895
 02896            if (inputChannels > 0)
 2897            {
 02898                resultChannels = inputChannels < resultChannels ? inputChannels : resultChannels ?? inputChannels;
 2899            }
 2900
 02901            var isTranscodingAudio = !IsCopyCodec(codec);
 2902
 02903            if (isTranscodingAudio)
 2904            {
 02905                var audioEncoder = GetAudioEncoder(state);
 02906                if (!_audioTranscodeChannelLookup.TryGetValue(audioEncoder, out var transcoderChannelLimit))
 2907                {
 2908                    // Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many c
 02909                    transcoderChannelLimit = 8;
 2910                }
 2911
 2912                // Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelL
 02913                resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? tr
 2914
 02915                if (request.TranscodingMaxAudioChannels < resultChannels)
 2916                {
 02917                    resultChannels = request.TranscodingMaxAudioChannels;
 2918                }
 2919
 2920                // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
 2921                // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_d
 02922                if (state.TranscodingType != TranscodingJobType.Progressive
 02923                    && ((resultChannels > 2 && resultChannels < 6) || resultChannels == 7))
 2924                {
 2925                    // We can let FFMpeg supply an extra LFE channel for 5ch and 7ch to make them 5.1 and 7.1
 02926                    if (resultChannels == 5)
 2927                    {
 02928                        resultChannels = 6;
 2929                    }
 02930                    else if (resultChannels == 7)
 2931                    {
 02932                        resultChannels = 8;
 2933                    }
 2934                    else
 2935                    {
 2936                        // For other weird layout, just downmix to stereo for compatibility
 02937                        resultChannels = 2;
 2938                    }
 2939                }
 2940            }
 2941
 02942            return resultChannels;
 2943        }
 2944
 2945        /// <summary>
 2946        /// Enforces the resolution limit.
 2947        /// </summary>
 2948        /// <param name="state">The state.</param>
 2949        public void EnforceResolutionLimit(EncodingJobInfo state)
 2950        {
 02951            var videoRequest = state.BaseRequest;
 2952
 2953            // Switch the incoming params to be ceilings rather than fixed values
 02954            videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
 02955            videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
 2956
 02957            videoRequest.Width = null;
 02958            videoRequest.Height = null;
 02959        }
 2960
 2961        /// <summary>
 2962        /// Gets the fast seek command line parameter.
 2963        /// </summary>
 2964        /// <param name="state">The state.</param>
 2965        /// <param name="options">The options.</param>
 2966        /// <param name="segmentContainer">Segment Container.</param>
 2967        /// <returns>System.String.</returns>
 2968        /// <value>The fast seek command line parameter.</value>
 2969        public string GetFastSeekCommandLineParameter(EncodingJobInfo state, EncodingOptions options, string segmentCont
 2970        {
 02971            var time = state.BaseRequest.StartTimeTicks ?? 0;
 02972            var maxTime = state.RunTimeTicks ?? 0;
 02973            var seekParam = string.Empty;
 2974
 02975            if (time > 0)
 2976            {
 2977                // For direct streaming/remuxing, HLS segments start at keyframes.
 2978                // However, ffmpeg will seek to previous keyframe when the exact frame time is the input
 2979                // Workaround this by adding 0.5s offset to the seeking time to get the exact keyframe on most videos.
 2980                // This will help subtitle syncing.
 02981                var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCod
 02982                var seekTick = isHlsRemuxing ? time + 5000000L : time;
 2983
 2984                // Seeking beyond EOF makes no sense in transcoding. Clamp the seekTick value to
 2985                // [0, RuntimeTicks - 5.0s], so that the muxer gets packets and avoid error codes.
 02986                if (maxTime > 0)
 2987                {
 02988                    seekTick = Math.Clamp(seekTick, 0, Math.Max(maxTime - 50000000L, 0));
 2989                }
 2990
 02991                seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(seekT
 2992
 02993                if (state.IsVideoRequest)
 2994                {
 2995                    // If we are remuxing, then the copied stream cannot be seeked accurately (it will seek to the neare
 2996                    // keyframe). If we are using fMP4, then force all other streams to use the same inaccurate seeking 
 2997                    // avoid A/V sync issues which cause playback issues on some devices.
 2998                    // When remuxing video, the segment start times correspond to key frames in the source stream, so th
 2999                    // option shouldn't change the seeked point that much.
 3000                    // Important: make sure not to use it with wtv because it breaks seeking
 03001                    if (state.TranscodingType is TranscodingJobType.Hls
 03002                        && string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase)
 03003                        && (IsCopyCodec(state.OutputVideoCodec) || IsCopyCodec(state.OutputAudioCodec))
 03004                        && !string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase))
 3005                    {
 03006                        seekParam += " -noaccurate_seek";
 3007                    }
 3008                }
 3009            }
 3010
 03011            return seekParam;
 3012        }
 3013
 3014        /// <summary>
 3015        /// Gets the map args.
 3016        /// </summary>
 3017        /// <param name="state">The state.</param>
 3018        /// <returns>System.String.</returns>
 3019        public string GetMapArgs(EncodingJobInfo state)
 3020        {
 3021            // If we don't have known media info
 3022            // If input is video, use -sn to drop subtitles
 3023            // Otherwise just return empty
 03024            if (state.VideoStream is null && state.AudioStream is null)
 3025            {
 03026                return state.IsInputVideo ? "-sn" : string.Empty;
 3027            }
 3028
 3029            // We have media info, but we don't know the stream index
 03030            if (state.VideoStream is not null && state.VideoStream.Index == -1)
 3031            {
 03032                return "-sn";
 3033            }
 3034
 3035            // We have media info, but we don't know the stream index
 03036            if (state.AudioStream is not null && state.AudioStream.Index == -1)
 3037            {
 03038                return state.IsInputVideo ? "-sn" : string.Empty;
 3039            }
 3040
 03041            var args = string.Empty;
 3042
 03043            if (state.VideoStream is not null)
 3044            {
 03045                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 3046
 03047                args += string.Format(
 03048                    CultureInfo.InvariantCulture,
 03049                    "-map 0:{0}",
 03050                    videoStreamIndex);
 3051            }
 3052            else
 3053            {
 3054                // No known video stream
 03055                args += "-vn";
 3056            }
 3057
 03058            if (state.AudioStream is not null)
 3059            {
 03060                int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
 03061                if (state.AudioStream.IsExternal)
 3062                {
 03063                    bool hasExternalGraphicsSubs = state.SubtitleStream is not null
 03064                        && ShouldEncodeSubtitle(state)
 03065                        && state.SubtitleStream.IsExternal
 03066                        && !state.SubtitleStream.IsTextSubtitleStream;
 03067                    int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
 3068
 03069                    args += string.Format(
 03070                        CultureInfo.InvariantCulture,
 03071                        " -map {0}:{1}",
 03072                        externalAudioMapIndex,
 03073                        audioStreamIndex);
 3074                }
 3075                else
 3076                {
 03077                    args += string.Format(
 03078                        CultureInfo.InvariantCulture,
 03079                        " -map 0:{0}",
 03080                        audioStreamIndex);
 3081                }
 3082            }
 3083            else
 3084            {
 03085                args += " -map -0:a";
 3086            }
 3087
 03088            var subtitleMethod = state.SubtitleDeliveryMethod;
 03089            if (state.SubtitleStream is null || subtitleMethod == SubtitleDeliveryMethod.Hls)
 3090            {
 03091                args += " -map -0:s";
 3092            }
 03093            else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
 3094            {
 03095                int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3096
 03097                args += string.Format(
 03098                    CultureInfo.InvariantCulture,
 03099                    " -map 0:{0}",
 03100                    subtitleStreamIndex);
 3101            }
 03102            else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
 3103            {
 03104                int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3105
 03106                args += string.Format(
 03107                    CultureInfo.InvariantCulture,
 03108                    " -map 1:{0} -sn",
 03109                    externalSubtitleStreamIndex);
 3110            }
 3111
 03112            return args;
 3113        }
 3114
 3115        /// <summary>
 3116        /// Gets the negative map args by filters.
 3117        /// </summary>
 3118        /// <param name="state">The state.</param>
 3119        /// <param name="videoProcessFilters">The videoProcessFilters.</param>
 3120        /// <returns>System.String.</returns>
 3121        public string GetNegativeMapArgsByFilters(EncodingJobInfo state, string videoProcessFilters)
 3122        {
 03123            string args = string.Empty;
 3124
 3125            // http://ffmpeg.org/ffmpeg-all.html#toc-Complex-filtergraphs-1
 03126            if (state.VideoStream is not null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordina
 3127            {
 03128                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 3129
 03130                args += string.Format(
 03131                    CultureInfo.InvariantCulture,
 03132                    "-map -0:{0} ",
 03133                    videoStreamIndex);
 3134            }
 3135
 03136            return args;
 3137        }
 3138
 3139        /// <summary>
 3140        /// Determines which stream will be used for playback.
 3141        /// </summary>
 3142        /// <param name="allStream">All stream.</param>
 3143        /// <param name="desiredIndex">Index of the desired.</param>
 3144        /// <param name="type">The type.</param>
 3145        /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
 3146        /// <returns>MediaStream.</returns>
 3147        public MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, b
 3148        {
 03149            var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
 3150
 03151            if (desiredIndex.HasValue)
 3152            {
 03153                var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
 3154
 03155                if (stream is not null)
 3156                {
 03157                    return stream;
 3158                }
 3159            }
 3160
 03161            if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
 3162            {
 03163                return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
 03164                       streams.FirstOrDefault();
 3165            }
 3166
 3167            // Just return the first one
 03168            return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
 3169        }
 3170
 3171        public static (int? Width, int? Height) GetFixedOutputSize(
 3172            int? videoWidth,
 3173            int? videoHeight,
 3174            int? requestedWidth,
 3175            int? requestedHeight,
 3176            int? requestedMaxWidth,
 3177            int? requestedMaxHeight)
 3178        {
 03179            if (!videoWidth.HasValue && !requestedWidth.HasValue)
 3180            {
 03181                return (null, null);
 3182            }
 3183
 03184            if (!videoHeight.HasValue && !requestedHeight.HasValue)
 3185            {
 03186                return (null, null);
 3187            }
 3188
 03189            int inputWidth = Convert.ToInt32(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture);
 03190            int inputHeight = Convert.ToInt32(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture);
 03191            int outputWidth = requestedWidth ?? inputWidth;
 03192            int outputHeight = requestedHeight ?? inputHeight;
 3193
 3194            // Don't transcode video to bigger than 4k when using HW.
 03195            int maximumWidth = Math.Min(requestedMaxWidth ?? outputWidth, 4096);
 03196            int maximumHeight = Math.Min(requestedMaxHeight ?? outputHeight, 4096);
 3197
 03198            if (outputWidth > maximumWidth || outputHeight > maximumHeight)
 3199            {
 03200                var scaleW = (double)maximumWidth / outputWidth;
 03201                var scaleH = (double)maximumHeight / outputHeight;
 03202                var scale = Math.Min(scaleW, scaleH);
 03203                outputWidth = Math.Min(maximumWidth, Convert.ToInt32(outputWidth * scale));
 03204                outputHeight = Math.Min(maximumHeight, Convert.ToInt32(outputHeight * scale));
 3205            }
 3206
 03207            outputWidth = 2 * (outputWidth / 2);
 03208            outputHeight = 2 * (outputHeight / 2);
 3209
 03210            return (outputWidth, outputHeight);
 3211        }
 3212
 3213        public static bool IsScaleRatioSupported(
 3214            int? videoWidth,
 3215            int? videoHeight,
 3216            int? requestedWidth,
 3217            int? requestedHeight,
 3218            int? requestedMaxWidth,
 3219            int? requestedMaxHeight,
 3220            double? maxScaleRatio)
 3221        {
 03222            var (outWidth, outHeight) = GetFixedOutputSize(
 03223                videoWidth,
 03224                videoHeight,
 03225                requestedWidth,
 03226                requestedHeight,
 03227                requestedMaxWidth,
 03228                requestedMaxHeight);
 3229
 03230            if (!videoWidth.HasValue
 03231                 || !videoHeight.HasValue
 03232                 || !outWidth.HasValue
 03233                 || !outHeight.HasValue
 03234                 || !maxScaleRatio.HasValue
 03235                 || (maxScaleRatio.Value < 1.0f))
 3236            {
 03237                return false;
 3238            }
 3239
 03240            var minScaleRatio = 1.0f / maxScaleRatio;
 03241            var scaleRatioW = (double)outWidth / (double)videoWidth;
 03242            var scaleRatioH = (double)outHeight / (double)videoHeight;
 3243
 03244            if (scaleRatioW < minScaleRatio
 03245                || scaleRatioW > maxScaleRatio
 03246                || scaleRatioH < minScaleRatio
 03247                || scaleRatioH > maxScaleRatio)
 3248            {
 03249                return false;
 3250            }
 3251
 03252            return true;
 3253        }
 3254
 3255        public static string GetHwScaleFilter(
 3256            string hwScalePrefix,
 3257            string hwScaleSuffix,
 3258            string videoFormat,
 3259            bool swapOutputWandH,
 3260            int? videoWidth,
 3261            int? videoHeight,
 3262            int? requestedWidth,
 3263            int? requestedHeight,
 3264            int? requestedMaxWidth,
 3265            int? requestedMaxHeight)
 3266        {
 03267            var (outWidth, outHeight) = GetFixedOutputSize(
 03268                videoWidth,
 03269                videoHeight,
 03270                requestedWidth,
 03271                requestedHeight,
 03272                requestedMaxWidth,
 03273                requestedMaxHeight);
 3274
 03275            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03276            var isSizeFixed = !videoWidth.HasValue
 03277                || outWidth.Value != videoWidth.Value
 03278                || !videoHeight.HasValue
 03279                || outHeight.Value != videoHeight.Value;
 3280
 03281            var swpOutW = swapOutputWandH ? outHeight.Value : outWidth.Value;
 03282            var swpOutH = swapOutputWandH ? outWidth.Value : outHeight.Value;
 3283
 03284            var arg1 = isSizeFixed ? $"=w={swpOutW}:h={swpOutH}" : string.Empty;
 03285            var arg2 = isFormatFixed ? $"format={videoFormat}" : string.Empty;
 03286            if (isFormatFixed)
 3287            {
 03288                arg2 = (isSizeFixed ? ':' : '=') + arg2;
 3289            }
 3290
 03291            if (!string.IsNullOrEmpty(hwScaleSuffix) && (isSizeFixed || isFormatFixed))
 3292            {
 03293                return string.Format(
 03294                    CultureInfo.InvariantCulture,
 03295                    "{0}_{1}{2}{3}",
 03296                    hwScalePrefix ?? "scale",
 03297                    hwScaleSuffix,
 03298                    arg1,
 03299                    arg2);
 3300            }
 3301
 03302            return string.Empty;
 3303        }
 3304
 3305        public static string GetGraphicalSubPreProcessFilters(
 3306            int? videoWidth,
 3307            int? videoHeight,
 3308            int? subtitleWidth,
 3309            int? subtitleHeight,
 3310            int? requestedWidth,
 3311            int? requestedHeight,
 3312            int? requestedMaxWidth,
 3313            int? requestedMaxHeight)
 3314        {
 03315            var (outWidth, outHeight) = GetFixedOutputSize(
 03316                videoWidth,
 03317                videoHeight,
 03318                requestedWidth,
 03319                requestedHeight,
 03320                requestedMaxWidth,
 03321                requestedMaxHeight);
 3322
 03323            if (!outWidth.HasValue
 03324                || !outHeight.HasValue
 03325                || outWidth.Value <= 0
 03326                || outHeight.Value <= 0)
 3327            {
 03328                return string.Empty;
 3329            }
 3330
 3331            // Automatically add padding based on subtitle input
 03332            var filters = @"scale,scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:blac
 3333
 03334            if (subtitleWidth.HasValue
 03335                && subtitleHeight.HasValue
 03336                && subtitleWidth.Value > 0
 03337                && subtitleHeight.Value > 0)
 3338            {
 03339                var videoDar = (double)outWidth.Value / outHeight.Value;
 03340                var subtitleDar = (double)subtitleWidth.Value / subtitleHeight.Value;
 3341
 3342                // No need to add padding when DAR is the same -> 1080p PGSSUB on 2160p video
 03343                if (Math.Abs(videoDar - subtitleDar) < 0.01f)
 3344                {
 03345                    filters = @"scale,scale={0}:{1}:fast_bilinear";
 3346                }
 3347            }
 3348
 03349            return string.Format(
 03350                CultureInfo.InvariantCulture,
 03351                filters,
 03352                outWidth.Value,
 03353                outHeight.Value);
 3354        }
 3355
 3356        public static string GetAlphaSrcFilter(
 3357            EncodingJobInfo state,
 3358            int? videoWidth,
 3359            int? videoHeight,
 3360            int? requestedWidth,
 3361            int? requestedHeight,
 3362            int? requestedMaxWidth,
 3363            int? requestedMaxHeight,
 3364            float? framerate)
 3365        {
 03366            var reqTicks = state.BaseRequest.StartTimeTicks ?? 0;
 03367            var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture
 03368            var (outWidth, outHeight) = GetFixedOutputSize(
 03369                videoWidth,
 03370                videoHeight,
 03371                requestedWidth,
 03372                requestedHeight,
 03373                requestedMaxWidth,
 03374                requestedMaxHeight);
 3375
 03376            if (outWidth.HasValue && outHeight.HasValue)
 3377            {
 03378                return string.Format(
 03379                    CultureInfo.InvariantCulture,
 03380                    "alphasrc=s={0}x{1}:r={2}:start='{3}'",
 03381                    outWidth.Value,
 03382                    outHeight.Value,
 03383                    framerate ?? 25,
 03384                    reqTicks > 0 ? startTime : 0);
 3385            }
 3386
 03387            return string.Empty;
 3388        }
 3389
 3390        public static string GetSwScaleFilter(
 3391            EncodingJobInfo state,
 3392            EncodingOptions options,
 3393            string videoEncoder,
 3394            int? videoWidth,
 3395            int? videoHeight,
 3396            Video3DFormat? threedFormat,
 3397            int? requestedWidth,
 3398            int? requestedHeight,
 3399            int? requestedMaxWidth,
 3400            int? requestedMaxHeight)
 3401        {
 03402            var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 03403            var isMjpeg = videoEncoder is not null && videoEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase)
 03404            var scaleVal = isV4l2 ? 64 : 2;
 03405            var targetAr = isMjpeg ? "(a*sar)" : "a"; // manually calculate AR when using mjpeg encoder
 3406
 3407            // If fixed dimensions were supplied
 03408            if (requestedWidth.HasValue && requestedHeight.HasValue)
 3409            {
 03410                if (isV4l2)
 3411                {
 03412                    var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 03413                    var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3414
 03415                    return string.Format(
 03416                            CultureInfo.InvariantCulture,
 03417                            "scale=trunc({0}/64)*64:trunc({1}/2)*2",
 03418                            widthParam,
 03419                            heightParam);
 3420                }
 3421
 03422                return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
 3423            }
 3424
 3425            // If Max dimensions were supplied, for width selects lowest even number between input width and width req s
 3426
 03427            if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
 3428            {
 03429                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 03430                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3431
 03432                return string.Format(
 03433                    CultureInfo.InvariantCulture,
 03434                    @"scale=trunc(min(max(iw\,ih*{3})\,min({0}\,{1}*{3}))/{2})*{2}:trunc(min(max(iw/{3}\,ih)\,min({0}/{3
 03435                    maxWidthParam,
 03436                    maxHeightParam,
 03437                    scaleVal,
 03438                    targetAr);
 3439            }
 3440
 3441            // If a fixed width was requested
 03442            if (requestedWidth.HasValue)
 3443            {
 03444                if (threedFormat.HasValue)
 3445                {
 3446                    // This method can handle 0 being passed in for the requested height
 03447                    return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0);
 3448                }
 3449
 03450                var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 3451
 03452                return string.Format(
 03453                    CultureInfo.InvariantCulture,
 03454                    "scale={0}:trunc(ow/{1}/2)*2",
 03455                    widthParam,
 03456                    targetAr);
 3457            }
 3458
 3459            // If a fixed height was requested
 03460            if (requestedHeight.HasValue)
 3461            {
 03462                var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3463
 03464                return string.Format(
 03465                    CultureInfo.InvariantCulture,
 03466                    "scale=trunc(oh*{2}/{1})*{1}:{0}",
 03467                    heightParam,
 03468                    scaleVal,
 03469                    targetAr);
 3470            }
 3471
 3472            // If a max width was requested
 03473            if (requestedMaxWidth.HasValue)
 3474            {
 03475                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 3476
 03477                return string.Format(
 03478                    CultureInfo.InvariantCulture,
 03479                    @"scale=trunc(min(max(iw\,ih*{2})\,{0})/{1})*{1}:trunc(ow/{2}/2)*2",
 03480                    maxWidthParam,
 03481                    scaleVal,
 03482                    targetAr);
 3483            }
 3484
 3485            // If a max height was requested
 03486            if (requestedMaxHeight.HasValue)
 3487            {
 03488                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3489
 03490                return string.Format(
 03491                    CultureInfo.InvariantCulture,
 03492                    @"scale=trunc(oh*{2}/{1})*{1}:min(max(iw/{2}\,ih)\,{0})",
 03493                    maxHeightParam,
 03494                    scaleVal,
 03495                    targetAr);
 3496            }
 3497
 03498            return string.Empty;
 3499        }
 3500
 3501        private static string GetFixedSwScaleFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight
 3502        {
 03503            var widthParam = requestedWidth.ToString(CultureInfo.InvariantCulture);
 03504            var heightParam = requestedHeight.ToString(CultureInfo.InvariantCulture);
 3505
 03506            string filter = null;
 3507
 03508            if (threedFormat.HasValue)
 3509            {
 03510                switch (threedFormat.Value)
 3511                {
 3512                    case Video3DFormat.HalfSideBySide:
 03513                        filter = @"crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(i
 3514                        // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars
 03515                        break;
 3516                    case Video3DFormat.FullSideBySide:
 03517                        filter = @"crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3518                        // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the s
 03519                        break;
 3520                    case Video3DFormat.HalfTopAndBottom:
 03521                        filter = @"crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(
 3522                        // htab crop height in half,scale to correct size, set the display aspect,crop out any black bar
 03523                        break;
 3524                    case Video3DFormat.FullTopAndBottom:
 03525                        filter = @"crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3526                        // ftab crop height in half, set the display aspect,crop out any black bars we may have made the
 3527                        break;
 3528                    default:
 3529                        break;
 3530                }
 3531            }
 3532
 3533            // default
 03534            if (filter is null)
 3535            {
 03536                if (requestedHeight > 0)
 3537                {
 03538                    filter = "scale=trunc({0}/2)*2:trunc({1}/2)*2";
 3539                }
 3540                else
 3541                {
 03542                    filter = "scale={0}:trunc({0}/a/2)*2";
 3543                }
 3544            }
 3545
 03546            return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam);
 3547        }
 3548
 3549        public static string GetSwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options)
 3550        {
 03551            var doubleRateDeint = options.DeinterlaceDoubleRate && state.VideoStream?.ReferenceFrameRate <= 30;
 03552            return string.Format(
 03553                CultureInfo.InvariantCulture,
 03554                "{0}={1}:-1:0",
 03555                options.DeinterlaceMethod.ToString().ToLowerInvariant(),
 03556                doubleRateDeint ? "1" : "0");
 3557        }
 3558
 3559        public string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix)
 3560        {
 03561            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03562            if (hwDeintSuffix.Contains("cuda", StringComparison.OrdinalIgnoreCase))
 3563            {
 03564                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3565
 03566                return string.Format(
 03567                    CultureInfo.InvariantCulture,
 03568                    "{0}_cuda={1}:-1:0",
 03569                    useBwdif ? "bwdif" : "yadif",
 03570                    doubleRateDeint ? "1" : "0");
 3571            }
 3572
 03573            if (hwDeintSuffix.Contains("opencl", StringComparison.OrdinalIgnoreCase))
 3574            {
 03575                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif;
 3576
 03577                if (_mediaEncoder.SupportsFilter("yadif_opencl")
 03578                    && _mediaEncoder.SupportsFilter("bwdif_opencl"))
 3579                {
 03580                    return string.Format(
 03581                        CultureInfo.InvariantCulture,
 03582                        "{0}_opencl={1}:-1:0",
 03583                        useBwdif ? "bwdif" : "yadif",
 03584                        doubleRateDeint ? "1" : "0");
 3585                }
 3586            }
 3587
 03588            if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
 3589            {
 03590                return string.Format(
 03591                    CultureInfo.InvariantCulture,
 03592                    "deinterlace_vaapi=rate={0}",
 03593                    doubleRateDeint ? "field" : "frame");
 3594            }
 3595
 03596            if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
 3597            {
 03598                return "deinterlace_qsv=mode=2";
 3599            }
 3600
 03601            if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
 3602            {
 03603                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3604
 03605                return string.Format(
 03606                    CultureInfo.InvariantCulture,
 03607                    "{0}_videotoolbox={1}:-1:0",
 03608                    useBwdif ? "bwdif" : "yadif",
 03609                    doubleRateDeint ? "1" : "0");
 3610            }
 3611
 03612            return string.Empty;
 3613        }
 3614
 3615        private string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat, bool forc
 3616        {
 03617            if (string.IsNullOrEmpty(hwTonemapSuffix))
 3618            {
 03619                return string.Empty;
 3620            }
 3621
 03622            var args = string.Empty;
 03623            var algorithm = options.TonemappingAlgorithm.ToString().ToLowerInvariant();
 03624            var mode = options.TonemappingMode.ToString().ToLowerInvariant();
 03625            var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 03626            var rangeString = range.ToString().ToLowerInvariant();
 3627
 03628            if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
 3629            {
 03630                var doVaVppProcamp = false;
 03631                var procampParams = string.Empty;
 03632                if (options.VppTonemappingBrightness != 0
 03633                    && options.VppTonemappingBrightness >= -100
 03634                    && options.VppTonemappingBrightness <= 100)
 3635                {
 03636                    procampParams += "procamp_vaapi=b={0}";
 03637                    doVaVppProcamp = true;
 3638                }
 3639
 03640                if (options.VppTonemappingContrast > 1
 03641                    && options.VppTonemappingContrast <= 10)
 3642                {
 03643                    procampParams += doVaVppProcamp ? ":c={1}" : "procamp_vaapi=c={1}";
 03644                    doVaVppProcamp = true;
 3645                }
 3646
 03647                args = procampParams + "{2}tonemap_vaapi=format={3}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
 3648
 03649                return string.Format(
 03650                        CultureInfo.InvariantCulture,
 03651                        args,
 03652                        options.VppTonemappingBrightness,
 03653                        options.VppTonemappingContrast,
 03654                        doVaVppProcamp ? "," : string.Empty,
 03655                        videoFormat ?? "nv12");
 3656            }
 3657            else
 3658            {
 03659                args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
 3660
 03661                var useLegacyTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode
 03662                                           && _legacyTonemapModes.Contains(options.TonemappingMode);
 3663
 03664                var useAdvancedTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegAdvancedTonemapMode
 03665                                              && _advancedTonemapModes.Contains(options.TonemappingMode);
 3666
 03667                if (useLegacyTonemapModes || useAdvancedTonemapModes)
 3668                {
 03669                    args += ":tonemap_mode={5}";
 3670                }
 3671
 03672                if (options.TonemappingParam != 0)
 3673                {
 03674                    args += ":param={6}";
 3675                }
 3676
 03677                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3678                {
 03679                    args += ":range={7}";
 3680                }
 3681            }
 3682
 03683            return string.Format(
 03684                    CultureInfo.InvariantCulture,
 03685                    args,
 03686                    hwTonemapSuffix,
 03687                    videoFormat ?? "nv12",
 03688                    algorithm,
 03689                    options.TonemappingPeak,
 03690                    options.TonemappingDesat,
 03691                    mode,
 03692                    options.TonemappingParam,
 03693                    rangeString);
 3694        }
 3695
 3696        private string GetLibplaceboFilter(
 3697            EncodingOptions options,
 3698            string videoFormat,
 3699            bool doTonemap,
 3700            int? videoWidth,
 3701            int? videoHeight,
 3702            int? requestedWidth,
 3703            int? requestedHeight,
 3704            int? requestedMaxWidth,
 3705            int? requestedMaxHeight,
 3706            bool forceFullRange)
 3707        {
 03708            var (outWidth, outHeight) = GetFixedOutputSize(
 03709                videoWidth,
 03710                videoHeight,
 03711                requestedWidth,
 03712                requestedHeight,
 03713                requestedMaxWidth,
 03714                requestedMaxHeight);
 3715
 03716            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03717            var isSizeFixed = !videoWidth.HasValue
 03718                || outWidth.Value != videoWidth.Value
 03719                || !videoHeight.HasValue
 03720                || outHeight.Value != videoHeight.Value;
 3721
 03722            var sizeArg = isSizeFixed ? (":w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty;
 03723            var formatArg = isFormatFixed ? (":format=" + videoFormat) : string.Empty;
 03724            var tonemapArg = string.Empty;
 3725
 03726            if (doTonemap)
 3727            {
 03728                var algorithm = options.TonemappingAlgorithm;
 03729                var algorithmString = "clip";
 03730                var mode = options.TonemappingMode;
 03731                var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 3732
 03733                if (algorithm == TonemappingAlgorithm.bt2390)
 3734                {
 03735                    algorithmString = "bt.2390";
 3736                }
 03737                else if (algorithm != TonemappingAlgorithm.none)
 3738                {
 03739                    algorithmString = algorithm.ToString().ToLowerInvariant();
 3740                }
 3741
 03742                tonemapArg = $":tonemapping={algorithmString}:peak_detect=0:color_primaries=bt709:color_trc=bt709:colors
 3743
 03744                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3745                {
 03746                    tonemapArg += ":range=" + range.ToString().ToLowerInvariant();
 3747                }
 3748            }
 3749
 03750            return string.Format(
 03751                CultureInfo.InvariantCulture,
 03752                "libplacebo=upscaler=none:downscaler=none{0}{1}{2}",
 03753                sizeArg,
 03754                formatArg,
 03755                tonemapArg);
 3756        }
 3757
 3758        public string GetVideoTransposeDirection(EncodingJobInfo state)
 3759        {
 03760            return (state.VideoStream?.Rotation ?? 0) switch
 03761            {
 03762                90 => "cclock",
 03763                180 => "reversal",
 03764                -90 => "clock",
 03765                -180 => "reversal",
 03766                _ => string.Empty
 03767            };
 3768        }
 3769
 3770        /// <summary>
 3771        /// Gets the parameter of software filter chain.
 3772        /// </summary>
 3773        /// <param name="state">Encoding state.</param>
 3774        /// <param name="options">Encoding options.</param>
 3775        /// <param name="vidEncoder">Video encoder to use.</param>
 3776        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3777        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetSwVidFilterChain(
 3778            EncodingJobInfo state,
 3779            EncodingOptions options,
 3780            string vidEncoder)
 3781        {
 03782            var inW = state.VideoStream?.Width;
 03783            var inH = state.VideoStream?.Height;
 03784            var reqW = state.BaseRequest.Width;
 03785            var reqH = state.BaseRequest.Height;
 03786            var reqMaxW = state.BaseRequest.MaxWidth;
 03787            var reqMaxH = state.BaseRequest.MaxHeight;
 03788            var threeDFormat = state.MediaSource.Video3DFormat;
 3789
 03790            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03791            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03792            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 03793            var isV4l2Encoder = vidEncoder.Contains("h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 3794
 03795            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03796            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03797            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03798            var doToneMap = IsSwTonemapAvailable(state, options);
 03799            var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI;
 3800
 03801            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03802            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03803            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 3804
 03805            var rotation = state.VideoStream?.Rotation ?? 0;
 03806            var swapWAndH = Math.Abs(rotation) == 90;
 03807            var swpInW = swapWAndH ? inH : inW;
 03808            var swpInH = swapWAndH ? inW : inH;
 3809
 3810            /* Make main filters for video stream */
 03811            var mainFilters = new List<string>();
 3812
 03813            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doToneMap));
 3814
 3815            // INPUT sw surface(memory/copy-back from vram)
 3816            // sw deint
 03817            if (doDeintH2645)
 3818            {
 03819                var deintFilter = GetSwDeinterlaceFilter(state, options);
 03820                mainFilters.Add(deintFilter);
 3821            }
 3822
 03823            var outFormat = isSwDecoder ? "yuv420p" : "nv12";
 03824            var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, r
 03825            if (isVaapiEncoder)
 3826            {
 03827                outFormat = "nv12";
 3828            }
 03829            else if (isV4l2Encoder)
 3830            {
 03831                outFormat = "yuv420p";
 3832            }
 3833
 3834            // sw scale
 03835            mainFilters.Add(swScaleFilter);
 3836
 3837            // sw tonemap
 03838            if (doToneMap)
 3839            {
 3840                // tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary
 03841                var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat;
 03842                var tonemapArgString = "tonemapx=tonemap={0}:desat={1}:peak={2}:t=bt709:m=bt709:p=bt709:format={3}";
 3843
 03844                if (options.TonemappingParam != 0)
 3845                {
 03846                    tonemapArgString += ":param={4}";
 3847                }
 3848
 03849                var range = options.TonemappingRange;
 03850                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3851                {
 03852                    tonemapArgString += ":range={5}";
 3853                }
 3854
 03855                var tonemapArgs = string.Format(
 03856                    CultureInfo.InvariantCulture,
 03857                    tonemapArgString,
 03858                    options.TonemappingAlgorithm,
 03859                    options.TonemappingDesat,
 03860                    options.TonemappingPeak,
 03861                    tonemapFormat,
 03862                    options.TonemappingParam,
 03863                    options.TonemappingRange);
 3864
 03865                mainFilters.Add(tonemapArgs);
 3866            }
 3867            else
 3868            {
 3869                // OUTPUT yuv420p/nv12 surface(memory)
 03870                mainFilters.Add("format=" + outFormat);
 3871            }
 3872
 3873            /* Make sub and overlay filters for subtitle stream */
 03874            var subFilters = new List<string>();
 03875            var overlayFilters = new List<string>();
 03876            if (hasTextSubs)
 3877            {
 3878                // subtitles=f='*.ass':alpha=0
 03879                var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03880                mainFilters.Add(textSubtitlesFilter);
 3881            }
 03882            else if (hasGraphicalSubs)
 3883            {
 03884                var subW = state.SubtitleStream?.Width;
 03885                var subH = state.SubtitleStream?.Height;
 03886                var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW
 03887                subFilters.Add(subPreProcFilters);
 03888                overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 3889            }
 3890
 03891            return (mainFilters, subFilters, overlayFilters);
 3892        }
 3893
 3894        /// <summary>
 3895        /// Gets the parameter of Nvidia NVENC filter chain.
 3896        /// </summary>
 3897        /// <param name="state">Encoding state.</param>
 3898        /// <param name="options">Encoding options.</param>
 3899        /// <param name="vidEncoder">Video encoder to use.</param>
 3900        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3901        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFilterChain(
 3902            EncodingJobInfo state,
 3903            EncodingOptions options,
 3904            string vidEncoder)
 3905        {
 03906            if (options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 3907            {
 03908                return (null, null, null);
 3909            }
 3910
 03911            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03912            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03913            var isSwEncoder = !vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 3914
 3915            // legacy cuvid pipeline(copy-back)
 03916            if ((isSwDecoder && isSwEncoder)
 03917                || !IsCudaFullSupported()
 03918                || !_mediaEncoder.SupportsFilter("alphasrc"))
 3919            {
 03920                return GetSwVidFilterChain(state, options, vidEncoder);
 3921            }
 3922
 3923            // preferred nvdec/cuvid + cuda filters + nvenc pipeline
 03924            return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 3925        }
 3926
 3927        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFiltersPrefe
 3928            EncodingJobInfo state,
 3929            EncodingOptions options,
 3930            string vidDecoder,
 3931            string vidEncoder)
 3932        {
 03933            var inW = state.VideoStream?.Width;
 03934            var inH = state.VideoStream?.Height;
 03935            var reqW = state.BaseRequest.Width;
 03936            var reqH = state.BaseRequest.Height;
 03937            var reqMaxW = state.BaseRequest.MaxWidth;
 03938            var reqMaxH = state.BaseRequest.MaxHeight;
 03939            var threeDFormat = state.MediaSource.Video3DFormat;
 3940
 03941            var isNvDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 03942            var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 03943            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03944            var isSwEncoder = !isNvencEncoder;
 03945            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 03946            var isCuInCuOut = isNvDecoder && isNvencEncoder;
 3947
 03948            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03949            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03950            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03951            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03952            var doCuTonemap = IsHwTonemapAvailable(state, options);
 3953
 03954            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03955            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03956            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 03957            var hasAssSubs = hasSubs
 03958                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 03959                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 03960            var subW = state.SubtitleStream?.Width;
 03961            var subH = state.SubtitleStream?.Height;
 3962
 03963            var rotation = state.VideoStream?.Rotation ?? 0;
 03964            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 03965            var doCuTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_cuda");
 03966            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isNvDecoder && doCuTranspose));
 03967            var swpInW = swapWAndH ? inH : inW;
 03968            var swpInH = swapWAndH ? inW : inH;
 3969
 3970            /* Make main filters for video stream */
 03971            var mainFilters = new List<string>();
 3972
 03973            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doCuTonemap));
 3974
 03975            if (isSwDecoder)
 3976            {
 3977                // INPUT sw surface(memory)
 3978                // sw deint
 03979                if (doDeintH2645)
 3980                {
 03981                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 03982                    mainFilters.Add(swDeintFilter);
 3983                }
 3984
 03985                var outFormat = doCuTonemap ? "yuv420p10le" : "yuv420p";
 03986                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 3987                // sw scale
 03988                mainFilters.Add(swScaleFilter);
 03989                mainFilters.Add($"format={outFormat}");
 3990
 3991                // sw => hw
 03992                if (doCuTonemap)
 3993                {
 03994                    mainFilters.Add("hwupload=derive_device=cuda");
 3995                }
 3996            }
 3997
 03998            if (isNvDecoder)
 3999            {
 4000                // INPUT cuda surface(vram)
 4001                // hw deint
 04002                if (doDeintH2645)
 4003                {
 04004                    var deintFilter = GetHwDeinterlaceFilter(state, options, "cuda");
 04005                    mainFilters.Add(deintFilter);
 4006                }
 4007
 4008                // hw transpose
 04009                if (doCuTranspose)
 4010                {
 04011                    mainFilters.Add($"transpose_cuda=dir={transposeDir}");
 4012                }
 4013
 04014                var isRext = IsVideoStreamHevcRext(state);
 04015                var outFormat = doCuTonemap ? (isRext ? "p010" : string.Empty) : "yuv420p";
 04016                var hwScaleFilter = GetHwScaleFilter("scale", "cuda", outFormat, false, swpInW, swpInH, reqW, reqH, reqM
 4017                // hw scale
 04018                mainFilters.Add(hwScaleFilter);
 4019            }
 4020
 4021            // hw tonemap
 04022            if (doCuTonemap)
 4023            {
 04024                var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p", isMjpegEncoder);
 04025                mainFilters.Add(tonemapFilter);
 4026            }
 4027
 04028            var memoryOutput = false;
 04029            var isUploadForCuTonemap = isSwDecoder && doCuTonemap;
 04030            if ((isNvDecoder && isSwEncoder) || (isUploadForCuTonemap && hasSubs))
 4031            {
 04032                memoryOutput = true;
 4033
 4034                // OUTPUT yuv420p surface(memory)
 04035                mainFilters.Add("hwdownload");
 04036                mainFilters.Add("format=yuv420p");
 4037            }
 4038
 4039            // OUTPUT yuv420p surface(memory)
 04040            if (isSwDecoder && isNvencEncoder && !isUploadForCuTonemap)
 4041            {
 04042                memoryOutput = true;
 4043            }
 4044
 04045            if (memoryOutput)
 4046            {
 4047                // text subtitles
 04048                if (hasTextSubs)
 4049                {
 04050                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04051                    mainFilters.Add(textSubtitlesFilter);
 4052                }
 4053            }
 4054
 4055            // OUTPUT cuda(yuv420p) surface(vram)
 4056
 4057            /* Make sub and overlay filters for subtitle stream */
 04058            var subFilters = new List<string>();
 04059            var overlayFilters = new List<string>();
 04060            if (isCuInCuOut)
 4061            {
 04062                if (hasSubs)
 4063                {
 04064                    var alphaFormatOpt = string.Empty;
 04065                    if (hasGraphicalSubs)
 4066                    {
 04067                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04068                        subFilters.Add(subPreProcFilters);
 04069                        subFilters.Add("format=yuva420p");
 4070                    }
 04071                    else if (hasTextSubs)
 4072                    {
 04073                        var framerate = state.VideoStream?.RealFrameRate;
 04074                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4075
 4076                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04077                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04078                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04079                        subFilters.Add(alphaSrcFilter);
 04080                        subFilters.Add("format=yuva420p");
 04081                        subFilters.Add(subTextSubtitlesFilter);
 4082
 04083                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayCudaAlphaFormat)
 04084                            ? ":alpha_format=premultiplied" : string.Empty;
 4085                    }
 4086
 04087                    subFilters.Add("hwupload=derive_device=cuda");
 04088                    overlayFilters.Add($"overlay_cuda=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 4089                }
 4090            }
 4091            else
 4092            {
 04093                if (hasGraphicalSubs)
 4094                {
 04095                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04096                    subFilters.Add(subPreProcFilters);
 04097                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4098                }
 4099            }
 4100
 04101            return (mainFilters, subFilters, overlayFilters);
 4102        }
 4103
 4104        /// <summary>
 4105        /// Gets the parameter of AMD AMF filter chain.
 4106        /// </summary>
 4107        /// <param name="state">Encoding state.</param>
 4108        /// <param name="options">Encoding options.</param>
 4109        /// <param name="vidEncoder">Video encoder to use.</param>
 4110        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4111        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVidFilterChain(
 4112            EncodingJobInfo state,
 4113            EncodingOptions options,
 4114            string vidEncoder)
 4115        {
 04116            if (options.HardwareAccelerationType != HardwareAccelerationType.amf)
 4117            {
 04118                return (null, null, null);
 4119            }
 4120
 04121            var isWindows = OperatingSystem.IsWindows();
 04122            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04123            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04124            var isSwEncoder = !vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04125            var isAmfDx11OclSupported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va") && IsOpenclFullSupported()
 4126
 4127            // legacy d3d11va pipeline(copy-back)
 04128            if ((isSwDecoder && isSwEncoder)
 04129                || !isAmfDx11OclSupported
 04130                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4131            {
 04132                return GetSwVidFilterChain(state, options, vidEncoder);
 4133            }
 4134
 4135            // preferred d3d11va + opencl filters + amf pipeline
 04136            return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4137        }
 4138
 4139        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdDx11VidFiltersPref
 4140            EncodingJobInfo state,
 4141            EncodingOptions options,
 4142            string vidDecoder,
 4143            string vidEncoder)
 4144        {
 04145            var inW = state.VideoStream?.Width;
 04146            var inH = state.VideoStream?.Height;
 04147            var reqW = state.BaseRequest.Width;
 04148            var reqH = state.BaseRequest.Height;
 04149            var reqMaxW = state.BaseRequest.MaxWidth;
 04150            var reqMaxH = state.BaseRequest.MaxHeight;
 04151            var threeDFormat = state.MediaSource.Video3DFormat;
 4152
 04153            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04154            var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04155            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04156            var isSwEncoder = !isAmfEncoder;
 04157            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04158            var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder;
 4159
 04160            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04161            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04162            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04163            var doOclTonemap = IsHwTonemapAvailable(state, options);
 4164
 04165            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04166            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04167            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04168            var hasAssSubs = hasSubs
 04169                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04170                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04171            var subW = state.SubtitleStream?.Width;
 04172            var subH = state.SubtitleStream?.Height;
 4173
 04174            var rotation = state.VideoStream?.Rotation ?? 0;
 04175            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04176            var doOclTranspose = !string.IsNullOrEmpty(transposeDir)
 04177                && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TransposeOpenclReversal);
 04178            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isD3d11vaDecoder && doOclTranspose));
 04179            var swpInW = swapWAndH ? inH : inW;
 04180            var swpInH = swapWAndH ? inW : inH;
 4181
 4182            /* Make main filters for video stream */
 04183            var mainFilters = new List<string>();
 4184
 04185            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 4186
 04187            if (isSwDecoder)
 4188            {
 4189                // INPUT sw surface(memory)
 4190                // sw deint
 04191                if (doDeintH2645)
 4192                {
 04193                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04194                    mainFilters.Add(swDeintFilter);
 4195                }
 4196
 04197                var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
 04198                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 4199                // sw scale
 04200                mainFilters.Add(swScaleFilter);
 04201                mainFilters.Add($"format={outFormat}");
 4202
 4203                // keep video at memory except ocl tonemap,
 4204                // since the overhead caused by hwupload >>> using sw filter.
 4205                // sw => hw
 04206                if (doOclTonemap)
 4207                {
 04208                    mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=24");
 04209                    mainFilters.Add("format=d3d11");
 04210                    mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4211                }
 4212            }
 4213
 04214            if (isD3d11vaDecoder)
 4215            {
 4216                // INPUT d3d11 surface(vram)
 4217                // map from d3d11va to opencl via d3d11-opencl interop.
 04218                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4219
 4220                // hw deint
 04221                if (doDeintH2645)
 4222                {
 04223                    var deintFilter = GetHwDeinterlaceFilter(state, options, "opencl");
 04224                    mainFilters.Add(deintFilter);
 4225                }
 4226
 4227                // hw transpose
 04228                if (doOclTranspose)
 4229                {
 04230                    mainFilters.Add($"transpose_opencl=dir={transposeDir}");
 4231                }
 4232
 04233                var outFormat = doOclTonemap ? string.Empty : "nv12";
 04234                var hwScaleFilter = GetHwScaleFilter("scale", "opencl", outFormat, false, swpInW, swpInH, reqW, reqH, re
 4235                // hw scale
 04236                mainFilters.Add(hwScaleFilter);
 4237            }
 4238
 4239            // hw tonemap
 04240            if (doOclTonemap)
 4241            {
 04242                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04243                mainFilters.Add(tonemapFilter);
 4244            }
 4245
 04246            var memoryOutput = false;
 04247            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04248            if (isD3d11vaDecoder && isSwEncoder)
 4249            {
 04250                memoryOutput = true;
 4251
 4252                // OUTPUT nv12 surface(memory)
 4253                // prefer hwmap to hwdownload on opencl.
 04254                var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap=mode=read";
 04255                mainFilters.Add(hwTransferFilter);
 04256                mainFilters.Add("format=nv12");
 4257            }
 4258
 4259            // OUTPUT yuv420p surface
 04260            if (isSwDecoder && isAmfEncoder && !isUploadForOclTonemap)
 4261            {
 04262                memoryOutput = true;
 4263            }
 4264
 04265            if (memoryOutput)
 4266            {
 4267                // text subtitles
 04268                if (hasTextSubs)
 4269                {
 04270                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04271                    mainFilters.Add(textSubtitlesFilter);
 4272                }
 4273            }
 4274
 04275            if ((isDxInDxOut || isUploadForOclTonemap) && !hasSubs)
 4276            {
 4277                // OUTPUT d3d11(nv12) surface(vram)
 4278                // reverse-mapping via d3d11-opencl interop.
 04279                mainFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04280                mainFilters.Add("format=d3d11");
 4281            }
 4282
 4283            /* Make sub and overlay filters for subtitle stream */
 04284            var subFilters = new List<string>();
 04285            var overlayFilters = new List<string>();
 04286            if (isDxInDxOut || isUploadForOclTonemap)
 4287            {
 04288                if (hasSubs)
 4289                {
 04290                    var alphaFormatOpt = string.Empty;
 04291                    if (hasGraphicalSubs)
 4292                    {
 04293                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04294                        subFilters.Add(subPreProcFilters);
 04295                        subFilters.Add("format=yuva420p");
 4296                    }
 04297                    else if (hasTextSubs)
 4298                    {
 04299                        var framerate = state.VideoStream?.RealFrameRate;
 04300                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4301
 4302                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04303                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04304                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04305                        subFilters.Add(alphaSrcFilter);
 04306                        subFilters.Add("format=yuva420p");
 04307                        subFilters.Add(subTextSubtitlesFilter);
 4308
 04309                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclAlphaForma
 04310                            ? ":alpha_format=premultiplied" : string.Empty;
 4311                    }
 4312
 04313                    subFilters.Add("hwupload=derive_device=opencl");
 04314                    overlayFilters.Add($"overlay_opencl=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 04315                    overlayFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04316                    overlayFilters.Add("format=d3d11");
 4317                }
 4318            }
 04319            else if (memoryOutput)
 4320            {
 04321                if (hasGraphicalSubs)
 4322                {
 04323                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04324                    subFilters.Add(subPreProcFilters);
 04325                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4326                }
 4327            }
 4328
 04329            return (mainFilters, subFilters, overlayFilters);
 4330        }
 4331
 4332        /// <summary>
 4333        /// Gets the parameter of Intel QSV filter chain.
 4334        /// </summary>
 4335        /// <param name="state">Encoding state.</param>
 4336        /// <param name="options">Encoding options.</param>
 4337        /// <param name="vidEncoder">Video encoder to use.</param>
 4338        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4339        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVidFilterChain(
 4340            EncodingJobInfo state,
 4341            EncodingOptions options,
 4342            string vidEncoder)
 4343        {
 04344            if (options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 4345            {
 04346                return (null, null, null);
 4347            }
 4348
 04349            var isWindows = OperatingSystem.IsWindows();
 04350            var isLinux = OperatingSystem.IsLinux();
 04351            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04352            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04353            var isSwEncoder = !vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04354            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 04355            var isIntelDx11OclSupported = isWindows
 04356                && _mediaEncoder.SupportsHwaccel("d3d11va")
 04357                && isQsvOclSupported;
 04358            var isIntelVaapiOclSupported = isLinux
 04359                && IsVaapiSupported(state)
 04360                && isQsvOclSupported;
 4361
 4362            // legacy qsv pipeline(copy-back)
 04363            if ((isSwDecoder && isSwEncoder)
 04364                || (!isIntelVaapiOclSupported && !isIntelDx11OclSupported)
 04365                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4366            {
 04367                return GetSwVidFilterChain(state, options, vidEncoder);
 4368            }
 4369
 4370            // preferred qsv(vaapi) + opencl filters pipeline
 04371            if (isIntelVaapiOclSupported)
 4372            {
 04373                return GetIntelQsvVaapiVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4374            }
 4375
 4376            // preferred qsv(d3d11) + opencl filters pipeline
 04377            if (isIntelDx11OclSupported)
 4378            {
 04379                return GetIntelQsvDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4380            }
 4381
 04382            return (null, null, null);
 4383        }
 4384
 4385        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvDx11VidFilter
 4386            EncodingJobInfo state,
 4387            EncodingOptions options,
 4388            string vidDecoder,
 4389            string vidEncoder)
 4390        {
 04391            var inW = state.VideoStream?.Width;
 04392            var inH = state.VideoStream?.Height;
 04393            var reqW = state.BaseRequest.Width;
 04394            var reqH = state.BaseRequest.Height;
 04395            var reqMaxW = state.BaseRequest.MaxWidth;
 04396            var reqMaxH = state.BaseRequest.MaxHeight;
 04397            var threeDFormat = state.MediaSource.Video3DFormat;
 4398
 04399            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04400            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04401            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04402            var isHwDecoder = isD3d11vaDecoder || isQsvDecoder;
 04403            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04404            var isSwEncoder = !isQsvEncoder;
 04405            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04406            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4407
 04408            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04409            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04410            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04411            var doVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04412            var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options);
 04413            var doTonemap = doVppTonemap || doOclTonemap;
 4414
 04415            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04416            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04417            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04418            var hasAssSubs = hasSubs
 04419                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04420                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04421            var subW = state.SubtitleStream?.Width;
 04422            var subH = state.SubtitleStream?.Height;
 4423
 04424            var rotation = state.VideoStream?.Rotation ?? 0;
 04425            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04426            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04427            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isD3d11vaDecoder || isQsvDecoder) && doVppTran
 04428            var swpInW = swapWAndH ? inH : inW;
 04429            var swpInH = swapWAndH ? inW : inH;
 4430
 4431            /* Make main filters for video stream */
 04432            var mainFilters = new List<string>();
 4433
 04434            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4435
 04436            if (isSwDecoder)
 4437            {
 4438                // INPUT sw surface(memory)
 4439                // sw deint
 04440                if (doDeintH2645)
 4441                {
 04442                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04443                    mainFilters.Add(swDeintFilter);
 4444                }
 4445
 04446                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04447                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04448                if (isMjpegEncoder && !doOclTonemap)
 4449                {
 4450                    // sw decoder + hw mjpeg encoder
 04451                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4452                }
 4453
 4454                // sw scale
 04455                mainFilters.Add(swScaleFilter);
 04456                mainFilters.Add($"format={outFormat}");
 4457
 4458                // keep video at memory except ocl tonemap,
 4459                // since the overhead caused by hwupload >>> using sw filter.
 4460                // sw => hw
 04461                if (doOclTonemap)
 4462                {
 04463                    mainFilters.Add("hwupload=derive_device=opencl");
 4464                }
 4465            }
 04466            else if (isD3d11vaDecoder || isQsvDecoder)
 4467            {
 04468                var isRext = IsVideoStreamHevcRext(state);
 04469                var twoPassVppTonemap = false;
 04470                var doVppFullRangeOut = isMjpegEncoder
 04471                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04472                var doVppScaleModeHq = isMjpegEncoder
 04473                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 04474                var doVppProcamp = false;
 04475                var procampParams = string.Empty;
 04476                var procampParamsString = string.Empty;
 04477                if (doVppTonemap)
 4478                {
 04479                    if (isRext)
 4480                    {
 4481                        // VPP tonemap requires p010 input
 04482                        twoPassVppTonemap = true;
 4483                    }
 4484
 04485                    if (options.VppTonemappingBrightness != 0
 04486                        && options.VppTonemappingBrightness >= -100
 04487                        && options.VppTonemappingBrightness <= 100)
 4488                    {
 04489                        procampParamsString += ":brightness={0}";
 04490                        twoPassVppTonemap = doVppProcamp = true;
 4491                    }
 4492
 04493                    if (options.VppTonemappingContrast > 1
 04494                        && options.VppTonemappingContrast <= 10)
 4495                    {
 04496                        procampParamsString += ":contrast={1}";
 04497                        twoPassVppTonemap = doVppProcamp = true;
 4498                    }
 4499
 04500                    if (doVppProcamp)
 4501                    {
 04502                        procampParamsString += ":procamp=1:async_depth=2";
 04503                        procampParams = string.Format(
 04504                            CultureInfo.InvariantCulture,
 04505                            procampParamsString,
 04506                            options.VppTonemappingBrightness,
 04507                            options.VppTonemappingContrast);
 4508                    }
 4509                }
 4510
 04511                var outFormat = doOclTonemap ? ((doVppTranspose || isRext) ? "p010" : string.Empty) : "nv12";
 04512                outFormat = twoPassVppTonemap ? "p010" : outFormat;
 4513
 04514                var swapOutputWandH = doVppTranspose && swapWAndH;
 04515                var hwScaleFilter = GetHwScaleFilter("vpp", "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, req
 4516
 4517                // d3d11va doesn't support dynamic pool size, use vpp filter ctx to relay
 4518                // to prevent encoder async and bframes from exhausting the decoder pool.
 04519                if (!string.IsNullOrEmpty(hwScaleFilter) && isD3d11vaDecoder)
 4520                {
 04521                    hwScaleFilter += ":passthrough=0";
 4522                }
 4523
 04524                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose)
 4525                {
 04526                    hwScaleFilter += $":transpose={transposeDir}";
 4527                }
 4528
 04529                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4530                {
 04531                    hwScaleFilter += (doVppFullRangeOut && !doOclTonemap) ? ":out_range=pc" : string.Empty;
 04532                    hwScaleFilter += doVppScaleModeHq ? ":scale_mode=hq" : string.Empty;
 4533                }
 4534
 04535                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTonemap)
 4536                {
 04537                    hwScaleFilter += doVppProcamp ? procampParams : (twoPassVppTonemap ? string.Empty : ":tonemap=1");
 4538                }
 4539
 04540                if (isD3d11vaDecoder)
 4541                {
 04542                    if (!string.IsNullOrEmpty(hwScaleFilter) || doDeintH2645)
 4543                    {
 4544                        // INPUT d3d11 surface(vram)
 4545                        // map from d3d11va to qsv.
 04546                        mainFilters.Add("hwmap=derive_device=qsv");
 4547                    }
 4548                }
 4549
 4550                // hw deint
 04551                if (doDeintH2645)
 4552                {
 04553                    var deintFilter = GetHwDeinterlaceFilter(state, options, "qsv");
 04554                    mainFilters.Add(deintFilter);
 4555                }
 4556
 4557                // hw transpose & scale & tonemap(w/o procamp)
 04558                mainFilters.Add(hwScaleFilter);
 4559
 4560                // hw tonemap(w/ procamp)
 04561                if (doVppTonemap && twoPassVppTonemap)
 4562                {
 04563                    mainFilters.Add("vpp_qsv=tonemap=1:format=nv12:async_depth=2");
 4564                }
 4565
 4566                // force bt709 just in case vpp tonemap is not triggered or using MSDK instead of VPL.
 04567                if (doVppTonemap)
 4568                {
 04569                    mainFilters.Add(GetOverwriteColorPropertiesParam(state, false));
 4570                }
 4571            }
 4572
 04573            if (doOclTonemap && isHwDecoder)
 4574            {
 4575                // map from qsv to opencl via qsv(d3d11)-opencl interop.
 04576                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4577            }
 4578
 4579            // hw tonemap
 04580            if (doOclTonemap)
 4581            {
 04582                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04583                mainFilters.Add(tonemapFilter);
 4584            }
 4585
 04586            var memoryOutput = false;
 04587            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04588            var isHwmapUsable = isSwEncoder && doOclTonemap;
 04589            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4590            {
 04591                memoryOutput = true;
 4592
 4593                // OUTPUT nv12 surface(memory)
 4594                // prefer hwmap to hwdownload on opencl.
 4595                // qsv hwmap is not fully implemented for the time being.
 04596                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04597                mainFilters.Add("format=nv12");
 4598            }
 4599
 4600            // OUTPUT nv12 surface(memory)
 04601            if (isSwDecoder && isQsvEncoder)
 4602            {
 04603                memoryOutput = true;
 4604            }
 4605
 04606            if (memoryOutput)
 4607            {
 4608                // text subtitles
 04609                if (hasTextSubs)
 4610                {
 04611                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04612                    mainFilters.Add(textSubtitlesFilter);
 4613                }
 4614            }
 4615
 04616            if (isQsvInQsvOut && doOclTonemap)
 4617            {
 4618                // OUTPUT qsv(nv12) surface(vram)
 4619                // reverse-mapping via qsv(d3d11)-opencl interop.
 04620                mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1");
 04621                mainFilters.Add("format=qsv");
 4622            }
 4623
 4624            /* Make sub and overlay filters for subtitle stream */
 04625            var subFilters = new List<string>();
 04626            var overlayFilters = new List<string>();
 04627            if (isQsvInQsvOut)
 4628            {
 04629                if (hasSubs)
 4630                {
 04631                    if (hasGraphicalSubs)
 4632                    {
 4633                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04634                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04635                        subFilters.Add(subPreProcFilters);
 04636                        subFilters.Add("format=bgra");
 4637                    }
 04638                    else if (hasTextSubs)
 4639                    {
 04640                        var framerate = state.VideoStream?.RealFrameRate;
 04641                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4642
 4643                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 04644                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04645                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04646                        subFilters.Add(alphaSrcFilter);
 04647                        subFilters.Add("format=bgra");
 04648                        subFilters.Add(subTextSubtitlesFilter);
 4649                    }
 4650
 4651                    // qsv requires a fixed pool size.
 4652                    // default to 64 otherwise it will fail on certain iGPU.
 04653                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4654
 04655                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04656                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04657                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04658                        : string.Empty;
 04659                    var overlayQsvFilter = string.Format(
 04660                        CultureInfo.InvariantCulture,
 04661                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04662                        overlaySize);
 04663                    overlayFilters.Add(overlayQsvFilter);
 4664                }
 4665            }
 04666            else if (memoryOutput)
 4667            {
 04668                if (hasGraphicalSubs)
 4669                {
 04670                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04671                    subFilters.Add(subPreProcFilters);
 04672                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4673                }
 4674            }
 4675
 04676            return (mainFilters, subFilters, overlayFilters);
 4677        }
 4678
 4679        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvVaapiVidFilte
 4680            EncodingJobInfo state,
 4681            EncodingOptions options,
 4682            string vidDecoder,
 4683            string vidEncoder)
 4684        {
 04685            var inW = state.VideoStream?.Width;
 04686            var inH = state.VideoStream?.Height;
 04687            var reqW = state.BaseRequest.Width;
 04688            var reqH = state.BaseRequest.Height;
 04689            var reqMaxW = state.BaseRequest.MaxWidth;
 04690            var reqMaxH = state.BaseRequest.MaxHeight;
 04691            var threeDFormat = state.MediaSource.Video3DFormat;
 4692
 04693            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04694            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04695            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04696            var isHwDecoder = isVaapiDecoder || isQsvDecoder;
 04697            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04698            var isSwEncoder = !isQsvEncoder;
 04699            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04700            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4701
 04702            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04703            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04704            var doVaVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04705            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04706            var doTonemap = doVaVppTonemap || doOclTonemap;
 04707            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4708
 04709            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04710            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04711            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04712            var hasAssSubs = hasSubs
 04713                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04714                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04715            var subW = state.SubtitleStream?.Width;
 04716            var subH = state.SubtitleStream?.Height;
 4717
 04718            var rotation = state.VideoStream?.Rotation ?? 0;
 04719            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04720            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04721            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isVaapiDecoder || isQsvDecoder) && doVppTransp
 04722            var swpInW = swapWAndH ? inH : inW;
 04723            var swpInH = swapWAndH ? inW : inH;
 4724
 4725            /* Make main filters for video stream */
 04726            var mainFilters = new List<string>();
 4727
 04728            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4729
 04730            if (isSwDecoder)
 4731            {
 4732                // INPUT sw surface(memory)
 4733                // sw deint
 04734                if (doDeintH2645)
 4735                {
 04736                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04737                    mainFilters.Add(swDeintFilter);
 4738                }
 4739
 04740                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04741                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04742                if (isMjpegEncoder && !doOclTonemap)
 4743                {
 4744                    // sw decoder + hw mjpeg encoder
 04745                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4746                }
 4747
 4748                // sw scale
 04749                mainFilters.Add(swScaleFilter);
 04750                mainFilters.Add($"format={outFormat}");
 4751
 4752                // keep video at memory except ocl tonemap,
 4753                // since the overhead caused by hwupload >>> using sw filter.
 4754                // sw => hw
 04755                if (doOclTonemap)
 4756                {
 04757                    mainFilters.Add("hwupload=derive_device=opencl");
 4758                }
 4759            }
 04760            else if (isVaapiDecoder || isQsvDecoder)
 4761            {
 04762                var hwFilterSuffix = isVaapiDecoder ? "vaapi" : "qsv";
 04763                var isRext = IsVideoStreamHevcRext(state);
 04764                var doVppFullRangeOut = isMjpegEncoder
 04765                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04766                var doVppScaleModeHq = isMjpegEncoder
 04767                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 4768
 4769                // INPUT vaapi/qsv surface(vram)
 4770                // hw deint
 04771                if (doDeintH2645)
 4772                {
 04773                    var deintFilter = GetHwDeinterlaceFilter(state, options, hwFilterSuffix);
 04774                    mainFilters.Add(deintFilter);
 4775                }
 4776
 4777                // hw transpose(vaapi vpp)
 04778                if (isVaapiDecoder && doVppTranspose)
 4779                {
 04780                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 4781                }
 4782
 04783                var outFormat = doTonemap ? (((isQsvDecoder && doVppTranspose) || isRext) ? "p010" : string.Empty) : "nv
 04784                var swapOutputWandH = isQsvDecoder && doVppTranspose && swapWAndH;
 04785                var hwScalePrefix = isQsvDecoder ? "vpp" : "scale";
 04786                var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, hwFilterSuffix, outFormat, swapOutputWandH, swpInW, 
 4787
 04788                if (!string.IsNullOrEmpty(hwScaleFilter) && isQsvDecoder && doVppTranspose)
 4789                {
 04790                    hwScaleFilter += $":transpose={transposeDir}";
 4791                }
 4792
 04793                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4794                {
 04795                    hwScaleFilter += ((isQsvDecoder && !doVppFullRangeOut) || doOclTonemap) ? string.Empty : ":out_range
 04796                    hwScaleFilter += isQsvDecoder ? (doVppScaleModeHq ? ":scale_mode=hq" : string.Empty) : ":mode=hq";
 4797                }
 4798
 4799                // allocate extra pool sizes for vaapi vpp scale
 04800                if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder)
 4801                {
 04802                    hwScaleFilter += ":extra_hw_frames=24";
 4803                }
 4804
 4805                // hw transpose(qsv vpp) & scale
 04806                mainFilters.Add(hwScaleFilter);
 4807            }
 4808
 4809            // vaapi vpp tonemap
 04810            if (doVaVppTonemap && isHwDecoder)
 4811            {
 04812                if (isQsvDecoder)
 4813                {
 4814                    // map from qsv to vaapi.
 04815                    mainFilters.Add("hwmap=derive_device=vaapi");
 04816                    mainFilters.Add("format=vaapi");
 4817                }
 4818
 04819                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 04820                mainFilters.Add(tonemapFilter);
 4821
 04822                if (isQsvDecoder)
 4823                {
 4824                    // map from vaapi to qsv.
 04825                    mainFilters.Add("hwmap=derive_device=qsv");
 04826                    mainFilters.Add("format=qsv");
 4827                }
 4828            }
 4829
 04830            if (doOclTonemap && isHwDecoder)
 4831            {
 4832                // map from qsv to opencl via qsv(vaapi)-opencl interop.
 04833                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4834            }
 4835
 4836            // ocl tonemap
 04837            if (doOclTonemap)
 4838            {
 04839                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04840                mainFilters.Add(tonemapFilter);
 4841            }
 4842
 04843            var memoryOutput = false;
 04844            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04845            var isHwmapUsable = isSwEncoder && (doOclTonemap || isVaapiDecoder);
 04846            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4847            {
 04848                memoryOutput = true;
 4849
 4850                // OUTPUT nv12 surface(memory)
 4851                // prefer hwmap to hwdownload on opencl/vaapi.
 4852                // qsv hwmap is not fully implemented for the time being.
 04853                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04854                mainFilters.Add("format=nv12");
 4855            }
 4856
 4857            // OUTPUT nv12 surface(memory)
 04858            if (isSwDecoder && isQsvEncoder)
 4859            {
 04860                memoryOutput = true;
 4861            }
 4862
 04863            if (memoryOutput)
 4864            {
 4865                // text subtitles
 04866                if (hasTextSubs)
 4867                {
 04868                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04869                    mainFilters.Add(textSubtitlesFilter);
 4870                }
 4871            }
 4872
 04873            if (isQsvInQsvOut)
 4874            {
 04875                if (doOclTonemap)
 4876                {
 4877                    // OUTPUT qsv(nv12) surface(vram)
 4878                    // reverse-mapping via qsv(vaapi)-opencl interop.
 4879                    // add extra pool size to avoid the 'cannot allocate memory' error on hevc_qsv.
 04880                    mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1:extra_hw_frames=16");
 04881                    mainFilters.Add("format=qsv");
 4882                }
 04883                else if (isVaapiDecoder)
 4884                {
 04885                    mainFilters.Add("hwmap=derive_device=qsv");
 04886                    mainFilters.Add("format=qsv");
 4887                }
 4888            }
 4889
 4890            /* Make sub and overlay filters for subtitle stream */
 04891            var subFilters = new List<string>();
 04892            var overlayFilters = new List<string>();
 04893            if (isQsvInQsvOut)
 4894            {
 04895                if (hasSubs)
 4896                {
 04897                    if (hasGraphicalSubs)
 4898                    {
 4899                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04900                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04901                        subFilters.Add(subPreProcFilters);
 04902                        subFilters.Add("format=bgra");
 4903                    }
 04904                    else if (hasTextSubs)
 4905                    {
 04906                        var framerate = state.VideoStream?.RealFrameRate;
 04907                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4908
 04909                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04910                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04911                        subFilters.Add(alphaSrcFilter);
 04912                        subFilters.Add("format=bgra");
 04913                        subFilters.Add(subTextSubtitlesFilter);
 4914                    }
 4915
 4916                    // qsv requires a fixed pool size.
 4917                    // default to 64 otherwise it will fail on certain iGPU.
 04918                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4919
 04920                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04921                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04922                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04923                        : string.Empty;
 04924                    var overlayQsvFilter = string.Format(
 04925                        CultureInfo.InvariantCulture,
 04926                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04927                        overlaySize);
 04928                    overlayFilters.Add(overlayQsvFilter);
 4929                }
 4930            }
 04931            else if (memoryOutput)
 4932            {
 04933                if (hasGraphicalSubs)
 4934                {
 04935                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04936                    subFilters.Add(subPreProcFilters);
 04937                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4938                }
 4939            }
 4940
 04941            return (mainFilters, subFilters, overlayFilters);
 4942        }
 4943
 4944        /// <summary>
 4945        /// Gets the parameter of Intel/AMD VAAPI filter chain.
 4946        /// </summary>
 4947        /// <param name="state">Encoding state.</param>
 4948        /// <param name="options">Encoding options.</param>
 4949        /// <param name="vidEncoder">Video encoder to use.</param>
 4950        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4951        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiVidFilterChain(
 4952            EncodingJobInfo state,
 4953            EncodingOptions options,
 4954            string vidEncoder)
 4955        {
 04956            if (options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 4957            {
 04958                return (null, null, null);
 4959            }
 4960
 04961            var isLinux = OperatingSystem.IsLinux();
 04962            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04963            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04964            var isSwEncoder = !vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04965            var isVaapiFullSupported = isLinux && IsVaapiSupported(state) && IsVaapiFullSupported();
 04966            var isVaapiOclSupported = isVaapiFullSupported && IsOpenclFullSupported();
 04967            var isVaapiVkSupported = isVaapiFullSupported && IsVulkanFullSupported();
 4968
 4969            // legacy vaapi pipeline(copy-back)
 04970            if ((isSwDecoder && isSwEncoder)
 04971                || !isVaapiOclSupported
 04972                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4973            {
 04974                var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
 4975
 04976                if (!isSwEncoder)
 4977                {
 04978                    var newfilters = new List<string>();
 04979                    var noOverlay = swFilterChain.OverlayFilters.Count == 0;
 04980                    newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
 04981                    newfilters.Add("hwupload=derive_device=vaapi");
 4982
 04983                    var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
 04984                    var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
 04985                    return (mainFilters, swFilterChain.SubFilters, overlayFilters);
 4986                }
 4987
 04988                return swFilterChain;
 4989            }
 4990
 4991            // preferred vaapi + opencl filters pipeline
 04992            if (_mediaEncoder.IsVaapiDeviceInteliHD)
 4993            {
 4994                // Intel iHD path, with extra vpp tonemap and overlay support.
 04995                return GetIntelVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4996            }
 4997
 4998            // preferred vaapi + vulkan filters pipeline
 04999            if (_mediaEncoder.IsVaapiDeviceAmd
 05000                && isVaapiVkSupported
 05001                && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 05002                && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 5003            {
 5004                // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support.
 05005                return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5006            }
 5007
 5008            // Intel i965 and Amd legacy driver path, only featuring scale and deinterlace support.
 05009            return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5010        }
 5011
 5012        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVaapiFullVidFilt
 5013            EncodingJobInfo state,
 5014            EncodingOptions options,
 5015            string vidDecoder,
 5016            string vidEncoder)
 5017        {
 05018            var inW = state.VideoStream?.Width;
 05019            var inH = state.VideoStream?.Height;
 05020            var reqW = state.BaseRequest.Width;
 05021            var reqH = state.BaseRequest.Height;
 05022            var reqMaxW = state.BaseRequest.MaxWidth;
 05023            var reqMaxH = state.BaseRequest.MaxHeight;
 05024            var threeDFormat = state.MediaSource.Video3DFormat;
 5025
 05026            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05027            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05028            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05029            var isSwEncoder = !isVaapiEncoder;
 05030            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05031            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 5032
 05033            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05034            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05035            var doVaVppTonemap = isVaapiDecoder && IsIntelVppTonemapAvailable(state, options);
 05036            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 05037            var doTonemap = doVaVppTonemap || doOclTonemap;
 05038            var doDeintH2645 = doDeintH264 || doDeintHevc;
 5039
 05040            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05041            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05042            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05043            var hasAssSubs = hasSubs
 05044                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05045                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 05046            var subW = state.SubtitleStream?.Width;
 05047            var subH = state.SubtitleStream?.Height;
 5048
 05049            var rotation = state.VideoStream?.Rotation ?? 0;
 05050            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05051            var doVaVppTranspose = !string.IsNullOrEmpty(transposeDir);
 05052            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVaVppTranspose));
 05053            var swpInW = swapWAndH ? inH : inW;
 05054            var swpInH = swapWAndH ? inW : inH;
 5055
 5056            /* Make main filters for video stream */
 05057            var mainFilters = new List<string>();
 5058
 05059            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 5060
 05061            if (isSwDecoder)
 5062            {
 5063                // INPUT sw surface(memory)
 5064                // sw deint
 05065                if (doDeintH2645)
 5066                {
 05067                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05068                    mainFilters.Add(swDeintFilter);
 5069                }
 5070
 05071                var outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05072                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05073                if (isMjpegEncoder && !doOclTonemap)
 5074                {
 5075                    // sw decoder + hw mjpeg encoder
 05076                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5077                }
 5078
 5079                // sw scale
 05080                mainFilters.Add(swScaleFilter);
 05081                mainFilters.Add($"format={outFormat}");
 5082
 5083                // keep video at memory except ocl tonemap,
 5084                // since the overhead caused by hwupload >>> using sw filter.
 5085                // sw => hw
 05086                if (doOclTonemap)
 5087                {
 05088                    mainFilters.Add("hwupload=derive_device=opencl");
 5089                }
 5090            }
 05091            else if (isVaapiDecoder)
 5092            {
 05093                var isRext = IsVideoStreamHevcRext(state);
 5094
 5095                // INPUT vaapi surface(vram)
 5096                // hw deint
 05097                if (doDeintH2645)
 5098                {
 05099                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05100                    mainFilters.Add(deintFilter);
 5101                }
 5102
 5103                // hw transpose
 05104                if (doVaVppTranspose)
 5105                {
 05106                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 5107                }
 5108
 05109                var outFormat = doTonemap ? (isRext ? "p010" : string.Empty) : "nv12";
 05110                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, swpInW, swpInH, reqW, reqH, req
 5111
 05112                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5113                {
 05114                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05115                    hwScaleFilter += ":mode=hq";
 5116                }
 5117
 5118                // allocate extra pool sizes for vaapi vpp
 05119                if (!string.IsNullOrEmpty(hwScaleFilter))
 5120                {
 05121                    hwScaleFilter += ":extra_hw_frames=24";
 5122                }
 5123
 5124                // hw scale
 05125                mainFilters.Add(hwScaleFilter);
 5126            }
 5127
 5128            // vaapi vpp tonemap
 05129            if (doVaVppTonemap && isVaapiDecoder)
 5130            {
 05131                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 05132                mainFilters.Add(tonemapFilter);
 5133            }
 5134
 05135            if (doOclTonemap && isVaapiDecoder)
 5136            {
 5137                // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05138                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 5139            }
 5140
 5141            // ocl tonemap
 05142            if (doOclTonemap)
 5143            {
 05144                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05145                mainFilters.Add(tonemapFilter);
 5146            }
 5147
 05148            if (doOclTonemap && isVaInVaOut)
 5149            {
 5150                // OUTPUT vaapi(nv12) surface(vram)
 5151                // reverse-mapping via vaapi-opencl interop.
 05152                mainFilters.Add("hwmap=derive_device=vaapi:mode=write:reverse=1");
 05153                mainFilters.Add("format=vaapi");
 5154            }
 5155
 05156            var memoryOutput = false;
 05157            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 05158            var isHwmapNotUsable = isUploadForOclTonemap && isVaapiEncoder;
 05159            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap)
 5160            {
 05161                memoryOutput = true;
 5162
 5163                // OUTPUT nv12 surface(memory)
 5164                // prefer hwmap to hwdownload on opencl/vaapi.
 05165                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap=mode=read");
 05166                mainFilters.Add("format=nv12");
 5167            }
 5168
 5169            // OUTPUT nv12 surface(memory)
 05170            if (isSwDecoder && isVaapiEncoder)
 5171            {
 05172                memoryOutput = true;
 5173            }
 5174
 05175            if (memoryOutput)
 5176            {
 5177                // text subtitles
 05178                if (hasTextSubs)
 5179                {
 05180                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05181                    mainFilters.Add(textSubtitlesFilter);
 5182                }
 5183            }
 5184
 05185            if (memoryOutput && isVaapiEncoder)
 5186            {
 05187                if (!hasGraphicalSubs)
 5188                {
 05189                    mainFilters.Add("hwupload_vaapi");
 5190                }
 5191            }
 5192
 5193            /* Make sub and overlay filters for subtitle stream */
 05194            var subFilters = new List<string>();
 05195            var overlayFilters = new List<string>();
 05196            if (isVaInVaOut)
 5197            {
 05198                if (hasSubs)
 5199                {
 05200                    if (hasGraphicalSubs)
 5201                    {
 5202                        // overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead
 05203                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 05204                        subFilters.Add(subPreProcFilters);
 05205                        subFilters.Add("format=bgra");
 5206                    }
 05207                    else if (hasTextSubs)
 5208                    {
 05209                        var framerate = state.VideoStream?.RealFrameRate;
 05210                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5211
 05212                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 05213                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05214                        subFilters.Add(alphaSrcFilter);
 05215                        subFilters.Add("format=bgra");
 05216                        subFilters.Add(subTextSubtitlesFilter);
 5217                    }
 5218
 05219                    subFilters.Add("hwupload=derive_device=vaapi");
 5220
 05221                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 05222                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 05223                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 05224                        : string.Empty;
 05225                    var overlayVaapiFilter = string.Format(
 05226                        CultureInfo.InvariantCulture,
 05227                        "overlay_vaapi=eof_action=pass:repeatlast=0{0}",
 05228                        overlaySize);
 05229                    overlayFilters.Add(overlayVaapiFilter);
 5230                }
 5231            }
 05232            else if (memoryOutput)
 5233            {
 05234                if (hasGraphicalSubs)
 5235                {
 05236                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05237                    subFilters.Add(subPreProcFilters);
 05238                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5239
 05240                    if (isVaapiEncoder)
 5241                    {
 05242                        overlayFilters.Add("hwupload_vaapi");
 5243                    }
 5244                }
 5245            }
 5246
 05247            return (mainFilters, subFilters, overlayFilters);
 5248        }
 5249
 5250        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVaapiFullVidFilter
 5251            EncodingJobInfo state,
 5252            EncodingOptions options,
 5253            string vidDecoder,
 5254            string vidEncoder)
 5255        {
 05256            var inW = state.VideoStream?.Width;
 05257            var inH = state.VideoStream?.Height;
 05258            var reqW = state.BaseRequest.Width;
 05259            var reqH = state.BaseRequest.Height;
 05260            var reqMaxW = state.BaseRequest.MaxWidth;
 05261            var reqMaxH = state.BaseRequest.MaxHeight;
 05262            var threeDFormat = state.MediaSource.Video3DFormat;
 5263
 05264            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05265            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05266            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05267            var isSwEncoder = !isVaapiEncoder;
 05268            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5269
 05270            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05271            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05272            var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
 05273            var doDeintH2645 = doDeintH264 || doDeintHevc;
 5274
 05275            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05276            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05277            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05278            var hasAssSubs = hasSubs
 05279                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05280                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5281
 05282            var rotation = state.VideoStream?.Rotation ?? 0;
 05283            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05284            var doVkTranspose = isVaapiDecoder && !string.IsNullOrEmpty(transposeDir);
 05285            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVkTranspose));
 05286            var swpInW = swapWAndH ? inH : inW;
 05287            var swpInH = swapWAndH ? inW : inH;
 5288
 5289            /* Make main filters for video stream */
 05290            var mainFilters = new List<string>();
 5291
 05292            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doVkTonemap));
 5293
 05294            if (isSwDecoder)
 5295            {
 5296                // INPUT sw surface(memory)
 5297                // sw deint
 05298                if (doDeintH2645)
 5299                {
 05300                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05301                    mainFilters.Add(swDeintFilter);
 5302                }
 5303
 05304                if (doVkTonemap || hasSubs)
 5305                {
 5306                    // sw => hw
 05307                    mainFilters.Add("hwupload=derive_device=vulkan");
 05308                    mainFilters.Add("format=vulkan");
 5309                }
 5310                else
 5311                {
 5312                    // sw scale
 05313                    var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW,
 05314                    mainFilters.Add(swScaleFilter);
 05315                    mainFilters.Add("format=nv12");
 5316                }
 5317            }
 05318            else if (isVaapiDecoder)
 5319            {
 5320                // INPUT vaapi surface(vram)
 05321                if (doVkTranspose || doVkTonemap || hasSubs)
 5322                {
 5323                    // map from vaapi to vulkan/drm via interop (Polaris/gfx8+).
 05324                    if (_mediaEncoder.EncoderVersion >= _minFFmpegAlteredVaVkInterop)
 5325                    {
 05326                        if (doVkTranspose || !_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5327                        {
 5328                            // disable the indirect va-drm-vk mapping since it's no longer reliable.
 05329                            mainFilters.Add("hwmap=derive_device=drm");
 05330                            mainFilters.Add("format=drm_prime");
 05331                            mainFilters.Add("hwmap=derive_device=vulkan");
 05332                            mainFilters.Add("format=vulkan");
 5333
 5334                            // workaround for libplacebo using the imported vulkan frame on gfx8.
 05335                            if (!_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5336                            {
 05337                                mainFilters.Add("scale_vulkan");
 5338                            }
 5339                        }
 05340                        else if (doVkTonemap || hasSubs)
 5341                        {
 5342                            // non ad-hoc libplacebo also accepts drm_prime direct input.
 05343                            mainFilters.Add("hwmap=derive_device=drm");
 05344                            mainFilters.Add("format=drm_prime");
 5345                        }
 5346                    }
 5347                    else // legacy va-vk mapping that works only in jellyfin-ffmpeg6
 5348                    {
 05349                        mainFilters.Add("hwmap=derive_device=vulkan");
 05350                        mainFilters.Add("format=vulkan");
 5351                    }
 5352                }
 5353                else
 5354                {
 5355                    // hw deint
 05356                    if (doDeintH2645)
 5357                    {
 05358                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05359                        mainFilters.Add(deintFilter);
 5360                    }
 5361
 5362                    // hw scale
 05363                    var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", "nv12", false, inW, inH, reqW, reqH, reqMaxW,
 5364
 05365                    if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder && !doVkTonemap)
 5366                    {
 05367                        hwScaleFilter += ":out_range=pc:mode=hq";
 5368                    }
 5369
 05370                    mainFilters.Add(hwScaleFilter);
 5371                }
 5372            }
 5373
 5374            // vk transpose
 05375            if (doVkTranspose)
 5376            {
 05377                if (string.Equals(transposeDir, "reversal", StringComparison.OrdinalIgnoreCase))
 5378                {
 05379                    mainFilters.Add("flip_vulkan");
 5380                }
 5381                else
 5382                {
 05383                    mainFilters.Add($"transpose_vulkan=dir={transposeDir}");
 5384                }
 5385            }
 5386
 5387            // vk libplacebo
 05388            if (doVkTonemap || hasSubs)
 5389            {
 05390                var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, req
 05391                mainFilters.Add(libplaceboFilter);
 05392                mainFilters.Add("format=vulkan");
 5393            }
 5394
 05395            if (doVkTonemap && !hasSubs)
 5396            {
 5397                // OUTPUT vaapi(nv12) surface(vram)
 5398                // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05399                mainFilters.Add("hwmap=derive_device=vaapi");
 05400                mainFilters.Add("format=vaapi");
 5401
 5402                // clear the surf->meta_offset and output nv12
 05403                mainFilters.Add("scale_vaapi=format=nv12");
 5404
 5405                // hw deint
 05406                if (doDeintH2645)
 5407                {
 05408                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05409                    mainFilters.Add(deintFilter);
 5410                }
 5411            }
 5412
 05413            if (!hasSubs)
 5414            {
 5415                // OUTPUT nv12 surface(memory)
 05416                if (isSwEncoder && (doVkTonemap || isVaapiDecoder))
 5417                {
 05418                    mainFilters.Add("hwdownload");
 05419                    mainFilters.Add("format=nv12");
 5420                }
 5421
 05422                if (isSwDecoder && isVaapiEncoder && !doVkTonemap)
 5423                {
 05424                    mainFilters.Add("hwupload_vaapi");
 5425                }
 5426            }
 5427
 5428            /* Make sub and overlay filters for subtitle stream */
 05429            var subFilters = new List<string>();
 05430            var overlayFilters = new List<string>();
 05431            if (hasSubs)
 5432            {
 05433                if (hasGraphicalSubs)
 5434                {
 05435                    var subW = state.SubtitleStream?.Width;
 05436                    var subH = state.SubtitleStream?.Height;
 05437                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05438                    subFilters.Add(subPreProcFilters);
 05439                    subFilters.Add("format=bgra");
 5440                }
 05441                else if (hasTextSubs)
 5442                {
 05443                    var framerate = state.VideoStream?.RealFrameRate;
 05444                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5445
 05446                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05447                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05448                    subFilters.Add(alphaSrcFilter);
 05449                    subFilters.Add("format=bgra");
 05450                    subFilters.Add(subTextSubtitlesFilter);
 5451                }
 5452
 05453                subFilters.Add("hwupload=derive_device=vulkan");
 05454                subFilters.Add("format=vulkan");
 5455
 05456                overlayFilters.Add("overlay_vulkan=eof_action=pass:repeatlast=0");
 5457
 05458                if (isSwEncoder)
 5459                {
 5460                    // OUTPUT nv12 surface(memory)
 05461                    overlayFilters.Add("scale_vulkan=format=nv12");
 05462                    overlayFilters.Add("hwdownload");
 05463                    overlayFilters.Add("format=nv12");
 5464                }
 05465                else if (isVaapiEncoder)
 5466                {
 5467                    // OUTPUT vaapi(nv12) surface(vram)
 5468                    // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05469                    overlayFilters.Add("hwmap=derive_device=vaapi");
 05470                    overlayFilters.Add("format=vaapi");
 5471
 5472                    // clear the surf->meta_offset and output nv12
 05473                    overlayFilters.Add("scale_vaapi=format=nv12");
 5474
 5475                    // hw deint
 05476                    if (doDeintH2645)
 5477                    {
 05478                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05479                        overlayFilters.Add(deintFilter);
 5480                    }
 5481                }
 5482            }
 5483
 05484            return (mainFilters, subFilters, overlayFilters);
 5485        }
 5486
 5487        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiLimitedVidFilter
 5488            EncodingJobInfo state,
 5489            EncodingOptions options,
 5490            string vidDecoder,
 5491            string vidEncoder)
 5492        {
 05493            var inW = state.VideoStream?.Width;
 05494            var inH = state.VideoStream?.Height;
 05495            var reqW = state.BaseRequest.Width;
 05496            var reqH = state.BaseRequest.Height;
 05497            var reqMaxW = state.BaseRequest.MaxWidth;
 05498            var reqMaxH = state.BaseRequest.MaxHeight;
 05499            var threeDFormat = state.MediaSource.Video3DFormat;
 5500
 05501            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05502            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05503            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05504            var isSwEncoder = !isVaapiEncoder;
 05505            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05506            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 05507            var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965;
 05508            var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd;
 5509
 05510            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05511            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05512            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05513            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5514
 05515            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05516            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05517            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 5518
 05519            var rotation = state.VideoStream?.Rotation ?? 0;
 05520            var swapWAndH = Math.Abs(rotation) == 90 && isSwDecoder;
 05521            var swpInW = swapWAndH ? inH : inW;
 05522            var swpInH = swapWAndH ? inW : inH;
 5523
 5524            /* Make main filters for video stream */
 05525            var mainFilters = new List<string>();
 5526
 05527            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5528
 05529            var outFormat = string.Empty;
 05530            if (isSwDecoder)
 5531            {
 5532                // INPUT sw surface(memory)
 5533                // sw deint
 05534                if (doDeintH2645)
 5535                {
 05536                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05537                    mainFilters.Add(swDeintFilter);
 5538                }
 5539
 05540                outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05541                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05542                if (isMjpegEncoder && !doOclTonemap)
 5543                {
 5544                    // sw decoder + hw mjpeg encoder
 05545                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5546                }
 5547
 5548                // sw scale
 05549                mainFilters.Add(swScaleFilter);
 05550                mainFilters.Add("format=" + outFormat);
 5551
 5552                // keep video at memory except ocl tonemap,
 5553                // since the overhead caused by hwupload >>> using sw filter.
 5554                // sw => hw
 05555                if (doOclTonemap)
 5556                {
 05557                    mainFilters.Add("hwupload=derive_device=opencl");
 5558                }
 5559            }
 05560            else if (isVaapiDecoder)
 5561            {
 5562                // INPUT vaapi surface(vram)
 5563                // hw deint
 05564                if (doDeintH2645)
 5565                {
 05566                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05567                    mainFilters.Add(deintFilter);
 5568                }
 5569
 05570                outFormat = doOclTonemap ? string.Empty : "nv12";
 05571                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, inW, inH, reqW, reqH, reqMaxW, 
 5572
 05573                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5574                {
 05575                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05576                    hwScaleFilter += ":mode=hq";
 5577                }
 5578
 5579                // allocate extra pool sizes for vaapi vpp
 05580                if (!string.IsNullOrEmpty(hwScaleFilter))
 5581                {
 05582                    hwScaleFilter += ":extra_hw_frames=24";
 5583                }
 5584
 5585                // hw scale
 05586                mainFilters.Add(hwScaleFilter);
 5587            }
 5588
 05589            if (doOclTonemap && isVaapiDecoder)
 5590            {
 05591                if (isi965Driver)
 5592                {
 5593                    // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05594                    mainFilters.Add("hwmap=derive_device=opencl");
 5595                }
 5596                else
 5597                {
 05598                    mainFilters.Add("hwdownload");
 05599                    mainFilters.Add("format=p010le");
 05600                    mainFilters.Add("hwupload=derive_device=opencl");
 5601                }
 5602            }
 5603
 5604            // ocl tonemap
 05605            if (doOclTonemap)
 5606            {
 05607                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05608                mainFilters.Add(tonemapFilter);
 5609            }
 5610
 05611            if (doOclTonemap && isVaInVaOut)
 5612            {
 05613                if (isi965Driver)
 5614                {
 5615                    // OUTPUT vaapi(nv12) surface(vram)
 5616                    // reverse-mapping via vaapi-opencl interop.
 05617                    mainFilters.Add("hwmap=derive_device=vaapi:reverse=1");
 05618                    mainFilters.Add("format=vaapi");
 5619                }
 5620            }
 5621
 05622            var memoryOutput = false;
 05623            var isUploadForOclTonemap = doOclTonemap && (isSwDecoder || (isVaapiDecoder && !isi965Driver));
 05624            var isHwmapNotUsable = hasGraphicalSubs || isUploadForOclTonemap;
 05625            var isHwmapForSubs = hasSubs && isVaapiDecoder;
 05626            var isHwUnmapForTextSubs = hasTextSubs && isVaInVaOut && !isUploadForOclTonemap;
 05627            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap || isHwmapForSubs)
 5628            {
 05629                memoryOutput = true;
 5630
 5631                // OUTPUT nv12 surface(memory)
 5632                // prefer hwmap to hwdownload on opencl/vaapi.
 05633                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap");
 05634                mainFilters.Add("format=nv12");
 5635            }
 5636
 5637            // OUTPUT nv12 surface(memory)
 05638            if (isSwDecoder && isVaapiEncoder)
 5639            {
 05640                memoryOutput = true;
 5641            }
 5642
 05643            if (memoryOutput)
 5644            {
 5645                // text subtitles
 05646                if (hasTextSubs)
 5647                {
 05648                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05649                    mainFilters.Add(textSubtitlesFilter);
 5650                }
 5651            }
 5652
 05653            if (isHwUnmapForTextSubs)
 5654            {
 05655                mainFilters.Add("hwmap");
 05656                mainFilters.Add("format=vaapi");
 5657            }
 05658            else if (memoryOutput && isVaapiEncoder)
 5659            {
 05660                if (!hasGraphicalSubs)
 5661                {
 05662                    mainFilters.Add("hwupload_vaapi");
 5663                }
 5664            }
 5665
 5666            /* Make sub and overlay filters for subtitle stream */
 05667            var subFilters = new List<string>();
 05668            var overlayFilters = new List<string>();
 05669            if (memoryOutput)
 5670            {
 05671                if (hasGraphicalSubs)
 5672                {
 05673                    var subW = state.SubtitleStream?.Width;
 05674                    var subH = state.SubtitleStream?.Height;
 05675                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05676                    subFilters.Add(subPreProcFilters);
 05677                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5678
 05679                    if (isVaapiEncoder)
 5680                    {
 05681                        overlayFilters.Add("hwupload_vaapi");
 5682                    }
 5683                }
 5684            }
 5685
 05686            return (mainFilters, subFilters, overlayFilters);
 5687        }
 5688
 5689        /// <summary>
 5690        /// Gets the parameter of Apple VideoToolBox filter chain.
 5691        /// </summary>
 5692        /// <param name="state">Encoding state.</param>
 5693        /// <param name="options">Encoding options.</param>
 5694        /// <param name="vidEncoder">Video encoder to use.</param>
 5695        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5696        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFilterChain(
 5697            EncodingJobInfo state,
 5698            EncodingOptions options,
 5699            string vidEncoder)
 5700        {
 05701            if (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 5702            {
 05703                return (null, null, null);
 5704            }
 5705
 5706            // ReSharper disable once InconsistentNaming
 05707            var isMacOS = OperatingSystem.IsMacOS();
 05708            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05709            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05710            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05711            var isVtFullSupported = isMacOS && IsVideoToolboxFullSupported();
 5712
 5713            // legacy videotoolbox pipeline (disable hw filters)
 05714            if (!(isVtEncoder || isVtDecoder)
 05715                || !isVtFullSupported
 05716                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5717            {
 05718                return GetSwVidFilterChain(state, options, vidEncoder);
 5719            }
 5720
 5721            // preferred videotoolbox + metal filters pipeline
 05722            return GetAppleVidFiltersPreferred(state, options, vidDecoder, vidEncoder);
 5723        }
 5724
 5725        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFiltersPrefer
 5726            EncodingJobInfo state,
 5727            EncodingOptions options,
 5728            string vidDecoder,
 5729            string vidEncoder)
 5730        {
 05731            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05732            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05733            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5734
 05735            var inW = state.VideoStream?.Width;
 05736            var inH = state.VideoStream?.Height;
 05737            var reqW = state.BaseRequest.Width;
 05738            var reqH = state.BaseRequest.Height;
 05739            var reqMaxW = state.BaseRequest.MaxWidth;
 05740            var reqMaxH = state.BaseRequest.MaxHeight;
 05741            var threeDFormat = state.MediaSource.Video3DFormat;
 5742
 05743            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05744            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05745            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05746            var doVtTonemap = IsVideoToolboxTonemapAvailable(state, options);
 05747            var doMetalTonemap = !doVtTonemap && IsHwTonemapAvailable(state, options);
 05748            var usingHwSurface = isVtDecoder && (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface);
 5749
 05750            var rotation = state.VideoStream?.Rotation ?? 0;
 05751            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05752            var doVtTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_vt");
 05753            var swapWAndH = Math.Abs(rotation) == 90 && doVtTranspose;
 05754            var swpInW = swapWAndH ? inH : inW;
 05755            var swpInH = swapWAndH ? inW : inH;
 5756
 05757            var scaleFormat = string.Empty;
 5758            // Use P010 for Metal tone mapping, otherwise force an 8bit output.
 05759            if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase))
 5760            {
 05761                if (doMetalTonemap)
 5762                {
 05763                    if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 5764                    {
 05765                        scaleFormat = "p010le";
 5766                    }
 5767                }
 5768                else
 5769                {
 05770                    scaleFormat = "nv12";
 5771                }
 5772            }
 5773
 05774            var hwScaleFilter = GetHwScaleFilter("scale", "vt", scaleFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW,
 5775
 05776            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05777            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05778            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05779            var hasAssSubs = hasSubs
 05780                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05781                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5782
 5783            /* Make main filters for video stream */
 05784            var mainFilters = new List<string>();
 5785
 5786            // hw deint
 05787            if (doDeintH2645)
 5788            {
 05789                var deintFilter = GetHwDeinterlaceFilter(state, options, "videotoolbox");
 05790                mainFilters.Add(deintFilter);
 5791            }
 5792
 5793            // hw transpose
 05794            if (doVtTranspose)
 5795            {
 05796                mainFilters.Add($"transpose_vt=dir={transposeDir}");
 5797            }
 5798
 05799            if (doVtTonemap)
 5800            {
 5801                const string VtTonemapArgs = "color_matrix=bt709:color_primaries=bt709:color_transfer=bt709";
 5802
 5803                // scale_vt can handle scaling & tonemapping in one shot, just like vpp_qsv.
 05804                hwScaleFilter = string.IsNullOrEmpty(hwScaleFilter)
 05805                    ? "scale_vt=" + VtTonemapArgs
 05806                    : hwScaleFilter + ":" + VtTonemapArgs;
 5807            }
 5808
 5809            // hw scale & vt tonemap
 05810            mainFilters.Add(hwScaleFilter);
 5811
 5812            // Metal tonemap
 05813            if (doMetalTonemap)
 5814            {
 05815                var tonemapFilter = GetHwTonemapFilter(options, "videotoolbox", "nv12", isMjpegEncoder);
 05816                mainFilters.Add(tonemapFilter);
 5817            }
 5818
 5819            /* Make sub and overlay filters for subtitle stream */
 05820            var subFilters = new List<string>();
 05821            var overlayFilters = new List<string>();
 5822
 05823            if (hasSubs)
 5824            {
 05825                if (hasGraphicalSubs)
 5826                {
 05827                    var subW = state.SubtitleStream?.Width;
 05828                    var subH = state.SubtitleStream?.Height;
 05829                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05830                    subFilters.Add(subPreProcFilters);
 05831                    subFilters.Add("format=bgra");
 5832                }
 05833                else if (hasTextSubs)
 5834                {
 05835                    var framerate = state.VideoStream?.RealFrameRate;
 05836                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5837
 05838                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05839                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05840                    subFilters.Add(alphaSrcFilter);
 05841                    subFilters.Add("format=bgra");
 05842                    subFilters.Add(subTextSubtitlesFilter);
 5843                }
 5844
 05845                subFilters.Add("hwupload");
 05846                overlayFilters.Add("overlay_videotoolbox=eof_action=pass:repeatlast=0");
 5847            }
 5848
 05849            if (usingHwSurface)
 5850            {
 05851                if (!isVtEncoder)
 5852                {
 05853                    mainFilters.Add("hwdownload");
 05854                    mainFilters.Add("format=nv12");
 5855                }
 5856
 05857                return (mainFilters, subFilters, overlayFilters);
 5858            }
 5859
 5860            // For old jellyfin-ffmpeg that has broken hwsurface, add a hwupload
 05861            var needFiltering = mainFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05862                                subFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05863                                overlayFilters.Any(f => !string.IsNullOrEmpty(f));
 05864            if (needFiltering)
 5865            {
 5866                // INPUT videotoolbox/memory surface(vram/uma)
 5867                // this will pass-through automatically if in/out format matches.
 05868                mainFilters.Insert(0, "hwupload");
 05869                mainFilters.Insert(0, "format=nv12|p010le|videotoolbox_vld");
 5870
 05871                if (!isVtEncoder)
 5872                {
 05873                    mainFilters.Add("hwdownload");
 05874                    mainFilters.Add("format=nv12");
 5875                }
 5876            }
 5877
 05878            return (mainFilters, subFilters, overlayFilters);
 5879        }
 5880
 5881        /// <summary>
 5882        /// Gets the parameter of Rockchip RKMPP/RKRGA filter chain.
 5883        /// </summary>
 5884        /// <param name="state">Encoding state.</param>
 5885        /// <param name="options">Encoding options.</param>
 5886        /// <param name="vidEncoder">Video encoder to use.</param>
 5887        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5888        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFilterChain(
 5889            EncodingJobInfo state,
 5890            EncodingOptions options,
 5891            string vidEncoder)
 5892        {
 05893            if (options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 5894            {
 05895                return (null, null, null);
 5896            }
 5897
 05898            var isLinux = OperatingSystem.IsLinux();
 05899            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05900            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05901            var isSwEncoder = !vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05902            var isRkmppOclSupported = isLinux && IsRkmppFullSupported() && IsOpenclFullSupported();
 5903
 05904            if ((isSwDecoder && isSwEncoder)
 05905                || !isRkmppOclSupported
 05906                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5907            {
 05908                return GetSwVidFilterChain(state, options, vidEncoder);
 5909            }
 5910
 5911            // preferred rkmpp + rkrga + opencl filters pipeline
 05912            if (isRkmppOclSupported)
 5913            {
 05914                return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5915            }
 5916
 05917            return (null, null, null);
 5918        }
 5919
 5920        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFiltersPrefer
 5921            EncodingJobInfo state,
 5922            EncodingOptions options,
 5923            string vidDecoder,
 5924            string vidEncoder)
 5925        {
 05926            var inW = state.VideoStream?.Width;
 05927            var inH = state.VideoStream?.Height;
 05928            var reqW = state.BaseRequest.Width;
 05929            var reqH = state.BaseRequest.Height;
 05930            var reqMaxW = state.BaseRequest.MaxWidth;
 05931            var reqMaxH = state.BaseRequest.MaxHeight;
 05932            var threeDFormat = state.MediaSource.Video3DFormat;
 5933
 05934            var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05935            var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05936            var isSwDecoder = !isRkmppDecoder;
 05937            var isSwEncoder = !isRkmppEncoder;
 05938            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05939            var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder;
 05940            var isEncoderSupportAfbc = isRkmppEncoder
 05941                && (vidEncoder.Contains("h264", StringComparison.OrdinalIgnoreCase)
 05942                    || vidEncoder.Contains("hevc", StringComparison.OrdinalIgnoreCase));
 5943
 05944            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05945            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05946            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05947            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5948
 05949            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05950            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05951            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05952            var hasAssSubs = hasSubs
 05953                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05954                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 05955            var subW = state.SubtitleStream?.Width;
 05956            var subH = state.SubtitleStream?.Height;
 5957
 05958            var rotation = state.VideoStream?.Rotation ?? 0;
 05959            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05960            var doRkVppTranspose = !string.IsNullOrEmpty(transposeDir);
 05961            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isRkmppDecoder && doRkVppTranspose));
 05962            var swpInW = swapWAndH ? inH : inW;
 05963            var swpInH = swapWAndH ? inW : inH;
 5964
 5965            /* Make main filters for video stream */
 05966            var mainFilters = new List<string>();
 5967
 05968            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5969
 05970            if (isSwDecoder)
 5971            {
 5972                // INPUT sw surface(memory)
 5973                // sw deint
 05974                if (doDeintH2645)
 5975                {
 05976                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05977                    mainFilters.Add(swDeintFilter);
 5978                }
 5979
 05980                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 05981                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05982                if (isMjpegEncoder && !doOclTonemap)
 5983                {
 5984                    // sw decoder + hw mjpeg encoder
 05985                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5986                }
 5987
 05988                if (!string.IsNullOrEmpty(swScaleFilter))
 5989                {
 05990                    swScaleFilter += ":flags=fast_bilinear";
 5991                }
 5992
 5993                // sw scale
 05994                mainFilters.Add(swScaleFilter);
 05995                mainFilters.Add($"format={outFormat}");
 5996
 5997                // keep video at memory except ocl tonemap,
 5998                // since the overhead caused by hwupload >>> using sw filter.
 5999                // sw => hw
 06000                if (doOclTonemap)
 6001                {
 06002                    mainFilters.Add("hwupload=derive_device=opencl");
 6003                }
 6004            }
 06005            else if (isRkmppDecoder)
 6006            {
 6007                // INPUT rkmpp/drm surface(gem/dma-heap)
 6008
 06009                var isFullAfbcPipeline = isEncoderSupportAfbc && isDrmInDrmOut && !doOclTonemap;
 06010                var swapOutputWandH = doRkVppTranspose && swapWAndH;
 06011                var outFormat = doOclTonemap ? "p010" : "nv12";
 06012                var hwScaleFilter = GetHwScaleFilter("vpp", "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, r
 06013                var doScaling = !string.IsNullOrEmpty(GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, sw
 6014
 06015                if (!hasSubs
 06016                     || doRkVppTranspose
 06017                     || !isFullAfbcPipeline
 06018                     || doScaling)
 6019                {
 06020                    var isScaleRatioSupported = IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 6021
 6022                    // RGA3 hardware only support (1/8 ~ 8) scaling in each blit operation,
 6023                    // but in Trickplay there's a case: (3840/320 == 12), enable 2pass for it
 06024                    if (doScaling && !isScaleRatioSupported)
 6025                    {
 6026                        // Vendor provided BSP kernel has an RGA driver bug that causes the output to be corrupted for P
 6027                        // Use NV15 instead of P010 to avoid the issue.
 6028                        // SDR inputs are using BGRA formats already which is not affected.
 06029                        var intermediateFormat = doOclTonemap ? "nv15" : (isMjpegEncoder ? "bgra" : outFormat);
 06030                        var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={intermediateFormat}:force_o
 06031                        mainFilters.Add(hwScaleFilterFirstPass);
 6032                    }
 6033
 6034                    // The RKMPP MJPEG encoder on some newer chip models no longer supports RGB input.
 6035                    // Use 2pass here to enable RGA output of full-range YUV in the 2nd pass.
 06036                    if (isMjpegEncoder && !doOclTonemap && ((doScaling && isScaleRatioSupported) || !doScaling))
 6037                    {
 06038                        var hwScaleFilterFirstPass = "vpp_rkrga=format=bgra:afbc=1";
 06039                        mainFilters.Add(hwScaleFilterFirstPass);
 6040                    }
 6041
 06042                    if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose)
 6043                    {
 06044                        hwScaleFilter += $":transpose={transposeDir}";
 6045                    }
 6046
 6047                    // try enabling AFBC to save DDR bandwidth
 06048                    if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline)
 6049                    {
 06050                        hwScaleFilter += ":afbc=1";
 6051                    }
 6052
 6053                    // hw transpose & scale
 06054                    mainFilters.Add(hwScaleFilter);
 6055                }
 6056            }
 6057
 06058            if (doOclTonemap && isRkmppDecoder)
 6059            {
 6060                // map from rkmpp/drm to opencl via drm-opencl interop.
 06061                mainFilters.Add("hwmap=derive_device=opencl");
 6062            }
 6063
 6064            // ocl tonemap
 06065            if (doOclTonemap)
 6066            {
 06067                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 06068                mainFilters.Add(tonemapFilter);
 6069            }
 6070
 06071            var memoryOutput = false;
 06072            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 06073            if ((isRkmppDecoder && isSwEncoder) || isUploadForOclTonemap)
 6074            {
 06075                memoryOutput = true;
 6076
 6077                // OUTPUT nv12 surface(memory)
 06078                mainFilters.Add("hwdownload");
 06079                mainFilters.Add("format=nv12");
 6080            }
 6081
 6082            // OUTPUT nv12 surface(memory)
 06083            if (isSwDecoder && isRkmppEncoder)
 6084            {
 06085                memoryOutput = true;
 6086            }
 6087
 06088            if (memoryOutput)
 6089            {
 6090                // text subtitles
 06091                if (hasTextSubs)
 6092                {
 06093                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 06094                    mainFilters.Add(textSubtitlesFilter);
 6095                }
 6096            }
 6097
 06098            if (isDrmInDrmOut)
 6099            {
 06100                if (doOclTonemap)
 6101                {
 6102                    // OUTPUT drm(nv12) surface(gem/dma-heap)
 6103                    // reverse-mapping via drm-opencl interop.
 06104                    mainFilters.Add("hwmap=derive_device=rkmpp:reverse=1");
 06105                    mainFilters.Add("format=drm_prime");
 6106                }
 6107            }
 6108
 6109            /* Make sub and overlay filters for subtitle stream */
 06110            var subFilters = new List<string>();
 06111            var overlayFilters = new List<string>();
 06112            if (isDrmInDrmOut)
 6113            {
 06114                if (hasSubs)
 6115                {
 06116                    var subMaxH = 1080;
 06117                    if (hasGraphicalSubs)
 6118                    {
 06119                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 06120                        subFilters.Add(subPreProcFilters);
 06121                        subFilters.Add("format=bgra");
 6122                    }
 06123                    else if (hasTextSubs)
 6124                    {
 06125                        var framerate = state.VideoStream?.RealFrameRate;
 06126                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 6127
 6128                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 06129                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, subMaxH, subF
 06130                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 06131                        subFilters.Add(alphaSrcFilter);
 06132                        subFilters.Add("format=bgra");
 06133                        subFilters.Add(subTextSubtitlesFilter);
 6134                    }
 6135
 06136                    subFilters.Add("hwupload=derive_device=rkmpp");
 6137
 6138                    // offload 1080p+ subtitles swscale upscaling from CPU to RGA
 06139                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 06140                    if (overlayW.HasValue && overlayH.HasValue && overlayH.Value > subMaxH)
 6141                    {
 06142                        subFilters.Add($"vpp_rkrga=w={overlayW.Value}:h={overlayH.Value}:format=bgra:afbc=1");
 6143                    }
 6144
 6145                    // try enabling AFBC to save DDR bandwidth
 06146                    var hwOverlayFilter = "overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12";
 06147                    if (isEncoderSupportAfbc)
 6148                    {
 06149                        hwOverlayFilter += ":afbc=1";
 6150                    }
 6151
 06152                    overlayFilters.Add(hwOverlayFilter);
 6153                }
 6154            }
 06155            else if (memoryOutput)
 6156            {
 06157                if (hasGraphicalSubs)
 6158                {
 06159                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 06160                    subFilters.Add(subPreProcFilters);
 06161                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 6162                }
 6163            }
 6164
 06165            return (mainFilters, subFilters, overlayFilters);
 6166        }
 6167
 6168        /// <summary>
 6169        /// Gets the parameter of video processing filters.
 6170        /// </summary>
 6171        /// <param name="state">Encoding state.</param>
 6172        /// <param name="options">Encoding options.</param>
 6173        /// <param name="outputVideoCodec">Video codec to use.</param>
 6174        /// <returns>The video processing filters parameter.</returns>
 6175        public string GetVideoProcessingFilterParam(
 6176            EncodingJobInfo state,
 6177            EncodingOptions options,
 6178            string outputVideoCodec)
 6179        {
 06180            var videoStream = state.VideoStream;
 06181            if (videoStream is null)
 6182            {
 06183                return string.Empty;
 6184            }
 6185
 06186            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 06187            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 06188            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 6189
 6190            List<string> mainFilters;
 6191            List<string> subFilters;
 6192            List<string> overlayFilters;
 6193
 06194            (mainFilters, subFilters, overlayFilters) = options.HardwareAccelerationType switch
 06195            {
 06196                HardwareAccelerationType.vaapi => GetVaapiVidFilterChain(state, options, outputVideoCodec),
 06197                HardwareAccelerationType.amf => GetAmdVidFilterChain(state, options, outputVideoCodec),
 06198                HardwareAccelerationType.qsv => GetIntelVidFilterChain(state, options, outputVideoCodec),
 06199                HardwareAccelerationType.nvenc => GetNvidiaVidFilterChain(state, options, outputVideoCodec),
 06200                HardwareAccelerationType.videotoolbox => GetAppleVidFilterChain(state, options, outputVideoCodec),
 06201                HardwareAccelerationType.rkmpp => GetRkmppVidFilterChain(state, options, outputVideoCodec),
 06202                _ => GetSwVidFilterChain(state, options, outputVideoCodec),
 06203            };
 6204
 06205            mainFilters?.RemoveAll(string.IsNullOrEmpty);
 06206            subFilters?.RemoveAll(string.IsNullOrEmpty);
 06207            overlayFilters?.RemoveAll(string.IsNullOrEmpty);
 6208
 06209            var framerate = GetFramerateParam(state);
 06210            if (framerate.HasValue)
 6211            {
 06212                mainFilters.Insert(0, string.Format(
 06213                    CultureInfo.InvariantCulture,
 06214                    "fps={0}",
 06215                    framerate.Value));
 6216            }
 6217
 06218            var mainStr = string.Empty;
 06219            if (mainFilters?.Count > 0)
 6220            {
 06221                mainStr = string.Format(
 06222                    CultureInfo.InvariantCulture,
 06223                    "{0}",
 06224                    string.Join(',', mainFilters));
 6225            }
 6226
 06227            if (overlayFilters?.Count == 0)
 6228            {
 6229                // -vf "scale..."
 06230                return string.IsNullOrEmpty(mainStr) ? string.Empty : " -vf \"" + mainStr + "\"";
 6231            }
 6232
 06233            if (overlayFilters?.Count > 0
 06234                && subFilters?.Count > 0
 06235                && state.SubtitleStream is not null)
 6236            {
 6237                // overlay graphical/text subtitles
 06238                var subStr = string.Format(
 06239                        CultureInfo.InvariantCulture,
 06240                        "{0}",
 06241                        string.Join(',', subFilters));
 6242
 06243                var overlayStr = string.Format(
 06244                        CultureInfo.InvariantCulture,
 06245                        "{0}",
 06246                        string.Join(',', overlayFilters));
 6247
 06248                var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
 06249                var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 06250                var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 6251
 06252                if (hasSubs)
 6253                {
 6254                    // -filter_complex "[0:s]scale=s[sub]..."
 06255                    var filterStr = string.IsNullOrEmpty(mainStr)
 06256                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]{5}\""
 06257                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6258
 06259                    if (hasTextSubs)
 6260                    {
 06261                        filterStr = string.IsNullOrEmpty(mainStr)
 06262                            ? " -filter_complex \"{4}[sub];[0:{2}][sub]{5}\""
 06263                            : " -filter_complex \"{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6264                    }
 6265
 06266                    return string.Format(
 06267                        CultureInfo.InvariantCulture,
 06268                        filterStr,
 06269                        mapPrefix,
 06270                        subtitleStreamIndex,
 06271                        videoStreamIndex,
 06272                        mainStr,
 06273                        subStr,
 06274                        overlayStr);
 6275                }
 6276            }
 6277
 06278            return string.Empty;
 6279        }
 6280
 6281        public string GetOverwriteColorPropertiesParam(EncodingJobInfo state, bool isTonemapAvailable)
 6282        {
 06283            if (isTonemapAvailable)
 6284            {
 06285                return GetInputHdrParam(state.VideoStream?.ColorTransfer);
 6286            }
 6287
 06288            return GetOutputSdrParam(null);
 6289        }
 6290
 6291        public string GetInputHdrParam(string colorTransfer)
 6292        {
 06293            if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
 6294            {
 6295                // HLG
 06296                return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
 6297            }
 6298
 6299            // HDR10
 06300            return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc";
 6301        }
 6302
 6303        public string GetOutputSdrParam(string tonemappingRange)
 6304        {
 6305            // SDR
 06306            if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
 6307            {
 06308                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
 6309            }
 6310
 06311            if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
 6312            {
 06313                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc";
 6314            }
 6315
 06316            return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
 6317        }
 6318
 6319        public static int GetVideoColorBitDepth(EncodingJobInfo state)
 6320        {
 06321            var videoStream = state.VideoStream;
 06322            if (videoStream is not null)
 6323            {
 06324                if (videoStream.BitDepth.HasValue)
 6325                {
 06326                    return videoStream.BitDepth.Value;
 6327                }
 6328
 06329                if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
 06330                    || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
 06331                    || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 06332                    || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
 6333                {
 06334                    return 8;
 6335                }
 6336
 06337                if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 06338                    || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 06339                    || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
 6340                {
 06341                    return 10;
 6342                }
 6343
 06344                if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 06345                    || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 06346                    || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
 6347                {
 06348                    return 12;
 6349                }
 6350
 06351                return 8;
 6352            }
 6353
 06354            return 0;
 6355        }
 6356
 6357        /// <summary>
 6358        /// Gets the ffmpeg option string for the hardware accelerated video decoder.
 6359        /// </summary>
 6360        /// <param name="state">The encoding job info.</param>
 6361        /// <param name="options">The encoding options.</param>
 6362        /// <returns>The option string or null if none available.</returns>
 6363        protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
 6364        {
 06365            var videoStream = state.VideoStream;
 06366            var mediaSource = state.MediaSource;
 06367            if (videoStream is null || mediaSource is null)
 6368            {
 06369                return null;
 6370            }
 6371
 6372            // HWA decoders can handle both video files and video folders.
 06373            var videoType = state.VideoType;
 06374            if (videoType != VideoType.VideoFile
 06375                && videoType != VideoType.Iso
 06376                && videoType != VideoType.Dvd
 06377                && videoType != VideoType.BluRay)
 6378            {
 06379                return null;
 6380            }
 6381
 06382            if (IsCopyCodec(state.OutputVideoCodec))
 6383            {
 06384                return null;
 6385            }
 6386
 06387            var hardwareAccelerationType = options.HardwareAccelerationType;
 6388
 06389            if (!string.IsNullOrEmpty(videoStream.Codec) && hardwareAccelerationType != HardwareAccelerationType.none)
 6390            {
 06391                var bitDepth = GetVideoColorBitDepth(state);
 6392
 6393                // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support for most platforms
 06394                if (bitDepth == 10
 06395                    && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06396                         || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
 06397                         || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)
 06398                         || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)))
 6399                {
 6400                    // RKMPP has H.264 Hi10P decoder
 06401                    bool hasHardwareHi10P = hardwareAccelerationType == HardwareAccelerationType.rkmpp;
 6402
 6403                    // VideoToolbox on Apple Silicon has H.264 Hi10P mode enabled after macOS 14.6
 06404                    if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox)
 6405                    {
 06406                        var ver = Environment.OSVersion.Version;
 06407                        var arch = RuntimeInformation.OSArchitecture;
 06408                        if (arch.Equals(Architecture.Arm64) && ver >= new Version(14, 6))
 6409                        {
 06410                            hasHardwareHi10P = true;
 6411                        }
 6412                    }
 6413
 06414                    if (!hasHardwareHi10P
 06415                        && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6416                    {
 06417                        return null;
 6418                    }
 6419                }
 6420
 6421                // Block unsupported H.264 Hi422P and Hi444PP profiles, which can be encoded with 4:2:0 pixel format
 06422                if (string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)
 06423                    && ((videoStream.Profile?.Contains("4:2:2", StringComparison.OrdinalIgnoreCase) ?? false)
 06424                        || (videoStream.Profile?.Contains("4:4:4", StringComparison.OrdinalIgnoreCase) ?? false)))
 6425                {
 6426                    // VideoToolbox on Apple Silicon has H.264 Hi444PP and theoretically also has Hi422P
 06427                    if (!(hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06428                          && RuntimeInformation.OSArchitecture.Equals(Architecture.Arm64)))
 6429                    {
 06430                        return null;
 6431                    }
 6432                }
 6433
 06434                var decoder = hardwareAccelerationType switch
 06435                {
 06436                    HardwareAccelerationType.vaapi => GetVaapiVidDecoder(state, options, videoStream, bitDepth),
 06437                    HardwareAccelerationType.amf => GetAmfVidDecoder(state, options, videoStream, bitDepth),
 06438                    HardwareAccelerationType.qsv => GetQsvHwVidDecoder(state, options, videoStream, bitDepth),
 06439                    HardwareAccelerationType.nvenc => GetNvdecVidDecoder(state, options, videoStream, bitDepth),
 06440                    HardwareAccelerationType.videotoolbox => GetVideotoolboxVidDecoder(state, options, videoStream, bitD
 06441                    HardwareAccelerationType.rkmpp => GetRkmppVidDecoder(state, options, videoStream, bitDepth),
 06442                    _ => string.Empty
 06443                };
 6444
 06445                if (!string.IsNullOrEmpty(decoder))
 6446                {
 06447                    return decoder;
 6448                }
 6449            }
 6450
 6451            // leave blank so ffmpeg will decide
 06452            return null;
 6453        }
 6454
 6455        /// <summary>
 6456        /// Gets a hw decoder name.
 6457        /// </summary>
 6458        /// <param name="options">Encoding options.</param>
 6459        /// <param name="decoderPrefix">Decoder prefix.</param>
 6460        /// <param name="decoderSuffix">Decoder suffix.</param>
 6461        /// <param name="videoCodec">Video codec to use.</param>
 6462        /// <param name="bitDepth">Video color bit depth.</param>
 6463        /// <returns>Hardware decoder name.</returns>
 6464        public string GetHwDecoderName(EncodingOptions options, string decoderPrefix, string decoderSuffix, string video
 6465        {
 06466            if (string.IsNullOrEmpty(decoderPrefix) || string.IsNullOrEmpty(decoderSuffix))
 6467            {
 06468                return null;
 6469            }
 6470
 06471            var decoderName = decoderPrefix + '_' + decoderSuffix;
 6472
 06473            var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains
 6474
 6475            // VideoToolbox decoders have built-in SW fallback
 06476            if (bitDepth == 10
 06477                && isCodecAvailable
 06478                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6479            {
 06480                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06481                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
 06482                    && !options.EnableDecodingColorDepth10Hevc)
 6483                {
 06484                    return null;
 6485                }
 6486
 06487                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06488                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06489                    && !options.EnableDecodingColorDepth10Vp9)
 6490                {
 06491                    return null;
 6492                }
 6493            }
 6494
 06495            if (string.Equals(decoderSuffix, "cuvid", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdec
 6496            {
 06497                return null;
 6498            }
 6499
 06500            if (string.Equals(decoderSuffix, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwD
 6501            {
 06502                return null;
 6503            }
 6504
 06505            if (string.Equals(decoderSuffix, "rkmpp", StringComparison.OrdinalIgnoreCase))
 6506            {
 06507                return null;
 6508            }
 6509
 06510            return isCodecAvailable ? (" -c:v " + decoderName) : null;
 6511        }
 6512
 6513        /// <summary>
 6514        /// Gets a hwaccel type to use as a hardware decoder depending on the system.
 6515        /// </summary>
 6516        /// <param name="state">Encoding state.</param>
 6517        /// <param name="options">Encoding options.</param>
 6518        /// <param name="videoCodec">Video codec to use.</param>
 6519        /// <param name="bitDepth">Video color bit depth.</param>
 6520        /// <param name="outputHwSurface">Specifies if output hw surface.</param>
 6521        /// <returns>Hardware accelerator type.</returns>
 6522        public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, int bitDepth, bo
 6523        {
 06524            var isWindows = OperatingSystem.IsWindows();
 06525            var isLinux = OperatingSystem.IsLinux();
 06526            var isMacOS = OperatingSystem.IsMacOS();
 06527            var isD3d11Supported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va");
 06528            var isVaapiSupported = isLinux && IsVaapiSupported(state);
 06529            var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported();
 06530            var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv");
 06531            var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
 06532            var isRkmppSupported = isLinux && IsRkmppFullSupported();
 06533            var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCas
 06534            var hardwareAccelerationType = options.HardwareAccelerationType;
 6535
 06536            var ffmpegVersion = _mediaEncoder.EncoderVersion;
 6537
 6538            // Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
 06539            var isAv1 = ffmpegVersion < _minFFmpegImplicitHwaccel
 06540                && string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
 6541
 6542            // Allow profile mismatch if decoding H.264 baseline with d3d11va and vaapi hwaccels.
 06543            var profileMismatch = string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
 06544                && string.Equals(state.VideoStream?.Profile, "baseline", StringComparison.OrdinalIgnoreCase);
 6545
 6546            // Disable the extra internal copy in nvdec. We already handle it in filter chain.
 06547            var nvdecNoInternalCopy = ffmpegVersion >= _minFFmpegHwaUnsafeOutput;
 6548
 6549            // Strip the display rotation side data from the transposed fmp4 output stream.
 06550            var stripRotationData = (state.VideoStream?.Rotation ?? 0) != 0
 06551                && ffmpegVersion >= _minFFmpegDisplayRotationOption;
 06552            var stripRotationDataArgs = stripRotationData ? " -display_rotation 0" : string.Empty;
 6553
 6554            // VideoToolbox decoders have built-in SW fallback
 06555            if (isCodecAvailable
 06556                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6557            {
 06558                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06559                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase))
 6560                {
 06561                    if (IsVideoStreamHevcRext(state))
 6562                    {
 06563                        if (bitDepth <= 10 && !options.EnableDecodingColorDepth10HevcRext)
 6564                        {
 06565                            return null;
 6566                        }
 6567
 06568                        if (bitDepth == 12 && !options.EnableDecodingColorDepth12HevcRext)
 6569                        {
 06570                            return null;
 6571                        }
 6572
 06573                        if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06574                            && !_mediaEncoder.IsVaapiDeviceInteliHD)
 6575                        {
 06576                            return null;
 6577                        }
 6578                    }
 06579                    else if (bitDepth == 10 && !options.EnableDecodingColorDepth10Hevc)
 6580                    {
 06581                        return null;
 6582                    }
 6583                }
 6584
 06585                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06586                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06587                    && bitDepth == 10
 06588                    && !options.EnableDecodingColorDepth10Vp9)
 6589                {
 06590                    return null;
 6591                }
 6592            }
 6593
 6594            // Intel qsv/d3d11va/vaapi
 06595            if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 6596            {
 06597                if (options.PreferSystemNativeHwDecoder)
 6598                {
 06599                    if (isVaapiSupported && isCodecAvailable)
 6600                    {
 06601                        return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + st
 06602                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " 
 6603                    }
 6604
 06605                    if (isD3d11Supported && isCodecAvailable)
 6606                    {
 06607                        return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + 
 06608                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 
 6609                    }
 6610                }
 6611                else
 6612                {
 06613                    if (isQsvSupported && isCodecAvailable)
 6614                    {
 06615                        return " -hwaccel qsv" + (outputHwSurface ? " -hwaccel_output_format qsv -noautorotate" + stripR
 6616                    }
 6617                }
 6618            }
 6619
 6620            // Nvidia cuda
 06621            if (hardwareAccelerationType == HardwareAccelerationType.nvenc)
 6622            {
 06623                if (isCudaSupported && isCodecAvailable)
 6624                {
 06625                    if (options.EnableEnhancedNvdecDecoder)
 6626                    {
 6627                        // set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support.
 06628                        return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stri
 06629                            + (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + 
 6630                    }
 6631
 6632                    // cuvid decoder doesn't have threading issue.
 06633                    return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stripRot
 6634                }
 6635            }
 6636
 6637            // Amd d3d11va
 06638            if (hardwareAccelerationType == HardwareAccelerationType.amf)
 6639            {
 06640                if (isD3d11Supported && isCodecAvailable)
 6641                {
 06642                    return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + stri
 06643                        + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 2" +
 6644                }
 6645            }
 6646
 6647            // Vaapi
 06648            if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06649                && isVaapiSupported
 06650                && isCodecAvailable)
 6651            {
 06652                return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + stripRotat
 06653                    + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1
 6654            }
 6655
 6656            // Apple videotoolbox
 06657            if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06658                && isVideotoolboxSupported
 06659                && isCodecAvailable)
 6660            {
 06661                return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string
 6662            }
 6663
 6664            // Rockchip rkmpp
 06665            if (hardwareAccelerationType == HardwareAccelerationType.rkmpp
 06666                && isRkmppSupported
 06667                && isCodecAvailable)
 6668            {
 06669                return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime -noautorotate" + stripR
 6670            }
 6671
 06672            return null;
 6673        }
 6674
 6675        public string GetQsvHwVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6676        {
 06677            var isWindows = OperatingSystem.IsWindows();
 06678            var isLinux = OperatingSystem.IsLinux();
 6679
 06680            if ((!isWindows && !isLinux)
 06681                || options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 6682            {
 06683                return null;
 6684            }
 6685
 06686            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 06687            var isIntelDx11OclSupported = isWindows
 06688                && _mediaEncoder.SupportsHwaccel("d3d11va")
 06689                && isQsvOclSupported;
 06690            var isIntelVaapiOclSupported = isLinux
 06691                && IsVaapiSupported(state)
 06692                && isQsvOclSupported;
 06693            var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
 06694                && _mediaEncoder.SupportsFilter("alphasrc");
 6695
 06696            var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06697                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06698            var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 06699            var is8_10_12bitSwFormatsQsv = is8_10bitSwFormatsQsv
 06700                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06701                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06702                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06703                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06704                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06705                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06706                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6707            // TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
 6708
 06709            if (is8bitSwFormatsQsv)
 6710            {
 06711                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06712                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6713                {
 06714                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6715                }
 6716
 06717                if (string.Equals(videoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase))
 6718                {
 06719                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6720                }
 6721
 06722                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6723                {
 06724                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6725                }
 6726
 06727                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6728                {
 06729                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6730                }
 6731            }
 6732
 06733            if (is8_10bitSwFormatsQsv)
 6734            {
 06735                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6736                {
 06737                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6738                }
 6739
 06740                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 6741                {
 06742                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6743                }
 6744            }
 6745
 06746            if (is8_10_12bitSwFormatsQsv)
 6747            {
 06748                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06749                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6750                {
 06751                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6752                }
 6753            }
 6754
 06755            return null;
 6756        }
 6757
 6758        public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6759        {
 06760            if ((!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
 06761                || options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 6762            {
 06763                return null;
 6764            }
 6765
 06766            var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
 06767            var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06768                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06769            var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06770            var is8_10_12bitSwFormatsNvdec = is8_10bitSwFormatsNvdec
 06771                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06772                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06773                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06774                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6775            // TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
 6776
 06777            if (is8bitSwFormatsNvdec)
 6778            {
 06779                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06780                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6781                {
 06782                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6783                }
 6784
 06785                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6786                {
 06787                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6788                }
 6789
 06790                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6791                {
 06792                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6793                }
 6794
 06795                if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6796                {
 06797                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpe
 6798                }
 6799
 06800                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6801                {
 06802                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6803                }
 6804            }
 6805
 06806            if (is8_10bitSwFormatsNvdec)
 6807            {
 06808                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6809                {
 06810                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6811                }
 6812
 06813                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6814                {
 06815                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6816                }
 6817            }
 6818
 06819            if (is8_10_12bitSwFormatsNvdec)
 6820            {
 06821                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06822                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6823                {
 06824                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6825                }
 6826            }
 6827
 06828            return null;
 6829        }
 6830
 6831        public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitD
 6832        {
 06833            if (!OperatingSystem.IsWindows()
 06834                || options.HardwareAccelerationType != HardwareAccelerationType.amf)
 6835            {
 06836                return null;
 6837            }
 6838
 06839            var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
 06840                && IsOpenclFullSupported()
 06841                && _mediaEncoder.SupportsFilter("alphasrc");
 06842            var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06843                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06844            var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 6845
 06846            if (is8bitSwFormatsAmf)
 6847            {
 06848                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06849                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6850                {
 06851                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6852                }
 6853
 06854                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6855                {
 06856                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6857                }
 6858
 06859                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6860                {
 06861                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6862                }
 6863            }
 6864
 06865            if (is8_10bitSwFormatsAmf)
 6866            {
 06867                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06868                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6869                {
 06870                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6871                }
 6872
 06873                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6874                {
 06875                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6876                }
 6877
 06878                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6879                {
 06880                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6881                }
 6882            }
 6883
 06884            return null;
 6885        }
 6886
 6887        public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6888        {
 06889            if (!OperatingSystem.IsLinux()
 06890                || options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 6891            {
 06892                return null;
 6893            }
 6894
 06895            var hwSurface = IsVaapiSupported(state)
 06896                && IsVaapiFullSupported()
 06897                && IsOpenclFullSupported()
 06898                && _mediaEncoder.SupportsFilter("alphasrc");
 06899            var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06900                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06901            var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06902            var is8_10_12bitSwFormatsVaapi = is8_10bitSwFormatsVaapi
 06903                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06904                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06905                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06906                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06907                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06908                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06909                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6910
 06911            if (is8bitSwFormatsVaapi)
 6912            {
 06913                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06914                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6915                {
 06916                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6917                }
 6918
 06919                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6920                {
 06921                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6922                }
 6923
 06924                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6925                {
 06926                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6927                }
 6928
 06929                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6930                {
 06931                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6932                }
 6933            }
 6934
 06935            if (is8_10bitSwFormatsVaapi)
 6936            {
 06937                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6938                {
 06939                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6940                }
 6941
 06942                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6943                {
 06944                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6945                }
 6946            }
 6947
 06948            if (is8_10_12bitSwFormatsVaapi)
 6949            {
 06950                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06951                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6952                {
 06953                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6954                }
 6955            }
 6956
 06957            return null;
 6958        }
 6959
 6960        public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream,
 6961        {
 06962            if (!OperatingSystem.IsMacOS()
 06963                || options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 6964            {
 06965                return null;
 6966            }
 6967
 06968            var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase
 06969                                    || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnore
 06970            var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, String
 06971            var is8_10_12bitSwFormatsVt = is8_10bitSwFormatsVt
 06972                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06973                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06974                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06975                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06976                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06977                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06978                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 06979            var isAv1SupportedSwFormatsVt = is8_10bitSwFormatsVt || string.Equals("yuv420p12le", videoStream.PixelFormat
 6980
 6981            // The related patches make videotoolbox hardware surface working is only available in jellyfin-ffmpeg 7.0.1
 06982            bool useHwSurface = (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface) && IsVideoToolboxFullSupp
 6983
 06984            if (is8bitSwFormatsVt)
 6985            {
 06986                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6987                {
 06988                    return GetHwaccelType(state, options, "vp8", bitDepth, useHwSurface);
 6989                }
 6990            }
 6991
 06992            if (is8_10bitSwFormatsVt)
 6993            {
 06994                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06995                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6996                {
 06997                    return GetHwaccelType(state, options, "h264", bitDepth, useHwSurface);
 6998                }
 6999
 07000                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7001                {
 07002                    return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
 7003                }
 7004            }
 7005
 07006            if (is8_10_12bitSwFormatsVt)
 7007            {
 07008                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 07009                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7010                {
 07011                    return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
 7012                }
 7013
 07014                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 07015                    && isAv1SupportedSwFormatsVt
 07016                    && _mediaEncoder.IsVideoToolboxAv1DecodeAvailable)
 7017                {
 07018                    return GetHwaccelType(state, options, "av1", bitDepth, useHwSurface);
 7019                }
 7020            }
 7021
 07022            return null;
 7023        }
 7024
 7025        public string GetRkmppVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 7026        {
 07027            var isLinux = OperatingSystem.IsLinux();
 7028
 07029            if (!isLinux
 07030                || options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 7031            {
 07032                return null;
 7033            }
 7034
 07035            var inW = state.VideoStream?.Width;
 07036            var inH = state.VideoStream?.Height;
 07037            var reqW = state.BaseRequest.Width;
 07038            var reqH = state.BaseRequest.Height;
 07039            var reqMaxW = state.BaseRequest.MaxWidth;
 07040            var reqMaxH = state.BaseRequest.MaxHeight;
 7041
 7042            // rkrga RGA2e supports range from 1/16 to 16
 07043            if (!IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 16.0f))
 7044            {
 07045                return null;
 7046            }
 7047
 07048            var isRkmppOclSupported = IsRkmppFullSupported() && IsOpenclFullSupported();
 07049            var hwSurface = isRkmppOclSupported
 07050                && _mediaEncoder.SupportsFilter("alphasrc");
 7051
 7052            // rkrga RGA3 supports range from 1/8 to 8
 07053            var isAfbcSupported = hwSurface && IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 7054
 7055            // TODO: add more 8/10bit and 4:2:2 formats for Rkmpp after finishing the ffcheck tool
 07056            var is8bitSwFormatsRkmpp = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 07057                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 07058            var is10bitSwFormatsRkmpp = string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIg
 07059            var is8_10bitSwFormatsRkmpp = is8bitSwFormatsRkmpp || is10bitSwFormatsRkmpp;
 7060
 7061            // nv15 and nv20 are bit-stream only formats
 07062            if (is10bitSwFormatsRkmpp && !hwSurface)
 7063            {
 07064                return null;
 7065            }
 7066
 07067            if (is8bitSwFormatsRkmpp)
 7068            {
 07069                if (string.Equals(videoStream.Codec, "mpeg1video", StringComparison.OrdinalIgnoreCase))
 7070                {
 07071                    return GetHwaccelType(state, options, "mpeg1video", bitDepth, hwSurface);
 7072                }
 7073
 07074                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 7075                {
 07076                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 7077                }
 7078
 07079                if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 7080                {
 07081                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
 7082                }
 7083
 07084                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 7085                {
 07086                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 7087                }
 7088            }
 7089
 07090            if (is8_10bitSwFormatsRkmpp)
 7091            {
 07092                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 07093                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 7094                {
 07095                    var accelType = GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 07096                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7097                }
 7098
 07099                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 07100                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 7101                {
 07102                    var accelType = GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 07103                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7104                }
 7105
 07106                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 7107                {
 07108                    var accelType = GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 07109                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7110                }
 7111
 07112                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 7113                {
 7114                    // there's an issue about AV1 AFBC on RK3588, disable it for now until it's fixed upstream
 07115                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 7116                }
 7117            }
 7118
 07119            return null;
 7120        }
 7121
 7122        /// <summary>
 7123        /// Gets the number of threads.
 7124        /// </summary>
 7125        /// <param name="state">Encoding state.</param>
 7126        /// <param name="encodingOptions">Encoding options.</param>
 7127        /// <param name="outputVideoCodec">Video codec to use.</param>
 7128        /// <returns>Number of threads.</returns>
 7129#nullable enable
 7130        public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVide
 7131        {
 07132            var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount;
 7133
 07134            if (threads <= 0)
 7135            {
 7136                // Automatically set thread count
 07137                return 0;
 7138            }
 7139
 07140            return Math.Min(threads, Environment.ProcessorCount);
 7141        }
 7142
 7143#nullable disable
 7144        public void TryStreamCopy(EncodingJobInfo state, EncodingOptions options)
 7145        {
 07146            if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream))
 7147            {
 07148                state.OutputVideoCodec = "copy";
 7149            }
 7150            else
 7151            {
 07152                var user = state.User;
 7153
 7154                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07155                if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
 7156                {
 07157                    state.OutputVideoCodec = "copy";
 7158                }
 7159            }
 7160
 07161            var preventHlsAudioCopy = state.TranscodingType is TranscodingJobType.Hls
 07162                && state.VideoStream is not null
 07163                && !IsCopyCodec(state.OutputVideoCodec)
 07164                && options.HlsAudioSeekStrategy is HlsAudioSeekStrategy.TranscodeAudio;
 7165
 07166            if (state.AudioStream is not null
 07167                && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs)
 07168                && !preventHlsAudioCopy)
 7169            {
 07170                state.OutputAudioCodec = "copy";
 7171            }
 7172            else
 7173            {
 07174                var user = state.User;
 7175
 7176                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07177                if (user is not null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
 7178                {
 07179                    state.OutputAudioCodec = "copy";
 7180                }
 7181            }
 07182        }
 7183
 7184        private string GetFfmpegAnalyzeDurationArg(EncodingJobInfo state)
 7185        {
 07186            var analyzeDurationArgument = string.Empty;
 7187
 7188            // Apply -analyzeduration as per the environment variable,
 7189            // otherwise ffmpeg will break on certain files due to default value is 0.
 07190            var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
 7191
 07192            if (state.MediaSource.AnalyzeDurationMs > 0)
 7193            {
 07194                analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToStr
 7195            }
 07196            else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
 7197            {
 07198                analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
 7199            }
 7200
 07201            return analyzeDurationArgument;
 7202        }
 7203
 7204        private string GetFfmpegProbesizeArg()
 7205        {
 07206            var ffmpegProbeSize = _config.GetFFmpegProbeSize();
 7207
 07208            if (!string.IsNullOrEmpty(ffmpegProbeSize))
 7209            {
 07210                return $"-probesize {ffmpegProbeSize}";
 7211            }
 7212
 07213            return string.Empty;
 7214        }
 7215
 7216        public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
 7217        {
 07218            var inputModifier = string.Empty;
 07219            var analyzeDurationArgument = GetFfmpegAnalyzeDurationArg(state);
 7220
 07221            if (!string.IsNullOrEmpty(analyzeDurationArgument))
 7222            {
 07223                inputModifier += " " + analyzeDurationArgument;
 7224            }
 7225
 07226            inputModifier = inputModifier.Trim();
 7227
 7228            // Apply -probesize if configured
 07229            var ffmpegProbeSizeArgument = GetFfmpegProbesizeArg();
 7230
 07231            if (!string.IsNullOrEmpty(ffmpegProbeSizeArgument))
 7232            {
 07233                inputModifier += " " + ffmpegProbeSizeArgument;
 7234            }
 7235
 07236            var userAgentParam = GetUserAgentParam(state);
 7237
 07238            if (!string.IsNullOrEmpty(userAgentParam))
 7239            {
 07240                inputModifier += " " + userAgentParam;
 7241            }
 7242
 07243            inputModifier = inputModifier.Trim();
 7244
 07245            var refererParam = GetRefererParam(state);
 7246
 07247            if (!string.IsNullOrEmpty(refererParam))
 7248            {
 07249                inputModifier += " " + refererParam;
 7250            }
 7251
 07252            inputModifier = inputModifier.Trim();
 7253
 07254            inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions, segmentContainer);
 07255            inputModifier = inputModifier.Trim();
 7256
 07257            if (state.InputProtocol == MediaProtocol.Rtsp)
 7258            {
 07259                inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
 7260            }
 7261
 07262            if (!string.IsNullOrEmpty(state.InputAudioSync))
 7263            {
 07264                inputModifier += " -async " + state.InputAudioSync;
 7265            }
 7266
 7267            // The -fps_mode option cannot be applied to input
 07268            if (!string.IsNullOrEmpty(state.InputVideoSync) && _mediaEncoder.EncoderVersion < new Version(5, 1))
 7269            {
 07270                inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion);
 7271            }
 7272
 07273            int readrate = 0;
 07274            if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp)
 7275            {
 07276                readrate = 1;
 07277                inputModifier += " -re";
 7278            }
 07279            else if (encodingOptions.EnableSegmentDeletion
 07280                && state.VideoStream is not null
 07281                && state.TranscodingType == TranscodingJobType.Hls
 07282                && IsCopyCodec(state.OutputVideoCodec)
 07283                && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateOption)
 7284            {
 7285                // Set an input read rate limit 10x for using SegmentDeletion with stream-copy
 7286                // to prevent ffmpeg from exiting prematurely (due to fast drive)
 07287                readrate = 10;
 07288                inputModifier += $" -readrate {readrate}";
 7289            }
 7290
 7291            // Set a larger catchup value to revert to the old behavior,
 7292            // otherwise, remuxing might stall due to this new option
 07293            if (readrate > 0 && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateCatchupOption)
 7294            {
 07295                inputModifier += $" -readrate_catchup {readrate * 100}";
 7296            }
 7297
 07298            var flags = new List<string>();
 07299            if (state.IgnoreInputDts)
 7300            {
 07301                flags.Add("+igndts");
 7302            }
 7303
 07304            if (state.IgnoreInputIndex)
 7305            {
 07306                flags.Add("+ignidx");
 7307            }
 7308
 07309            if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
 7310            {
 07311                flags.Add("+genpts");
 7312            }
 7313
 07314            if (state.DiscardCorruptFramesInput)
 7315            {
 07316                flags.Add("+discardcorrupt");
 7317            }
 7318
 07319            if (state.EnableFastSeekInput)
 7320            {
 07321                flags.Add("+fastseek");
 7322            }
 7323
 07324            if (flags.Count > 0)
 7325            {
 07326                inputModifier += " -fflags " + string.Join(string.Empty, flags);
 7327            }
 7328
 07329            if (state.IsVideoRequest)
 7330            {
 07331                if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && encodingOpt
 7332                {
 07333                    var inputFormat = GetInputFormat(state.InputContainer);
 07334                    if (!string.IsNullOrEmpty(inputFormat))
 7335                    {
 07336                        inputModifier += " -f " + inputFormat;
 7337                    }
 7338                }
 7339            }
 7340
 07341            if (state.MediaSource.RequiresLooping)
 7342            {
 07343                inputModifier += " -stream_loop -1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2";
 7344            }
 7345
 07346            return inputModifier;
 7347        }
 7348
 7349        public void AttachMediaSourceInfo(
 7350            EncodingJobInfo state,
 7351            EncodingOptions encodingOptions,
 7352            MediaSourceInfo mediaSource,
 7353            string requestedUrl)
 7354        {
 07355            ArgumentNullException.ThrowIfNull(state);
 7356
 07357            ArgumentNullException.ThrowIfNull(mediaSource);
 7358
 07359            var path = mediaSource.Path;
 07360            var protocol = mediaSource.Protocol;
 7361
 07362            if (!string.IsNullOrEmpty(mediaSource.EncoderPath) && mediaSource.EncoderProtocol.HasValue)
 7363            {
 07364                path = mediaSource.EncoderPath;
 07365                protocol = mediaSource.EncoderProtocol.Value;
 7366            }
 7367
 07368            state.MediaPath = path;
 07369            state.InputProtocol = protocol;
 07370            state.InputContainer = mediaSource.Container;
 07371            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07372            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 7373
 07374            state.IsoType = mediaSource.IsoType;
 7375
 07376            if (mediaSource.Timestamp.HasValue)
 7377            {
 07378                state.InputTimestamp = mediaSource.Timestamp.Value;
 7379            }
 7380
 07381            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07382            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 07383            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
 7384
 07385            if ((state.ReadInputAtNativeFramerate && !state.IsSegmentedLiveStream)
 07386                || (mediaSource.Protocol == MediaProtocol.File
 07387                && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)))
 7388            {
 07389                state.InputVideoSync = "-1";
 07390                state.InputAudioSync = "1";
 7391            }
 7392
 07393            if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase)
 07394                || string.Equals(mediaSource.Container, "asf", StringComparison.OrdinalIgnoreCase))
 7395            {
 7396                // Seeing some stuttering when transcoding wma to audio-only HLS
 07397                state.InputAudioSync = "1";
 7398            }
 7399
 07400            var mediaStreams = mediaSource.MediaStreams;
 7401
 07402            if (state.IsVideoRequest)
 7403            {
 07404                var videoRequest = state.BaseRequest;
 7405
 07406                if (string.IsNullOrEmpty(videoRequest.VideoCodec))
 7407                {
 07408                    if (string.IsNullOrEmpty(requestedUrl))
 7409                    {
 07410                        requestedUrl = "test." + videoRequest.Container;
 7411                    }
 7412
 07413                    videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
 7414                }
 7415
 07416                state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
 07417                state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Su
 07418                state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
 07419                state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
 7420
 07421                if (state.SubtitleStream is not null && !state.SubtitleStream.IsExternal)
 7422                {
 07423                    state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !
 7424                }
 7425
 07426                EnforceResolutionLimit(state);
 7427
 07428                NormalizeSubtitleEmbed(state);
 7429            }
 7430            else
 7431            {
 07432                state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
 7433            }
 7434
 07435            state.MediaSource = mediaSource;
 7436
 07437            var request = state.BaseRequest;
 07438            var supportedAudioCodecs = state.SupportedAudioCodecs;
 07439            if (request is not null && supportedAudioCodecs is not null && supportedAudioCodecs.Length > 0)
 7440            {
 07441                var supportedAudioCodecsList = supportedAudioCodecs.ToList();
 7442
 07443                ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
 7444
 07445                state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray();
 7446
 07447                request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(_mediaEncoder.CanEncodeToAudioCodec)
 07448                    ?? state.SupportedAudioCodecs.FirstOrDefault();
 7449            }
 7450
 07451            var supportedVideoCodecs = state.SupportedVideoCodecs;
 07452            if (request is not null && supportedVideoCodecs is not null && supportedVideoCodecs.Length > 0)
 7453            {
 07454                var supportedVideoCodecsList = supportedVideoCodecs.ToList();
 7455
 07456                ShiftVideoCodecsIfNeeded(supportedVideoCodecsList, encodingOptions);
 7457
 07458                state.SupportedVideoCodecs = supportedVideoCodecsList.ToArray();
 7459
 07460                request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
 7461            }
 07462        }
 7463
 7464        private void ShiftAudioCodecsIfNeeded(List<string> audioCodecs, MediaStream audioStream)
 7465        {
 7466            // No need to shift if there is only one supported audio codec.
 07467            if (audioCodecs.Count < 2)
 7468            {
 07469                return;
 7470            }
 7471
 07472            var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
 07473            var shiftAudioCodecs = new List<string>();
 07474            if (inputChannels >= 6)
 7475            {
 7476                // DTS and TrueHD are not supported by HLS
 7477                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07478                shiftAudioCodecs.Add("dts");
 07479                shiftAudioCodecs.Add("truehd");
 7480            }
 7481            else
 7482            {
 7483                // Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
 7484                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07485                shiftAudioCodecs.Add("ac3");
 07486                shiftAudioCodecs.Add("eac3");
 7487            }
 7488
 07489            if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7490            {
 07491                return;
 7492            }
 7493
 07494            while (shiftAudioCodecs.Contains(audioCodecs[0], StringComparison.OrdinalIgnoreCase))
 7495            {
 07496                var removed = audioCodecs[0];
 07497                audioCodecs.RemoveAt(0);
 07498                audioCodecs.Add(removed);
 7499            }
 07500        }
 7501
 7502        private void ShiftVideoCodecsIfNeeded(List<string> videoCodecs, EncodingOptions encodingOptions)
 7503        {
 7504            // No need to shift if there is only one supported video codec.
 07505            if (videoCodecs.Count < 2)
 7506            {
 07507                return;
 7508            }
 7509
 7510            // Shift codecs to the end of list if it's not allowed.
 07511            var shiftVideoCodecs = new List<string>();
 07512            if (!encodingOptions.AllowHevcEncoding)
 7513            {
 07514                shiftVideoCodecs.Add("hevc");
 07515                shiftVideoCodecs.Add("h265");
 7516            }
 7517
 07518            if (!encodingOptions.AllowAv1Encoding)
 7519            {
 07520                shiftVideoCodecs.Add("av1");
 7521            }
 7522
 07523            if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7524            {
 07525                return;
 7526            }
 7527
 07528            while (shiftVideoCodecs.Contains(videoCodecs[0], StringComparison.OrdinalIgnoreCase))
 7529            {
 07530                var removed = videoCodecs[0];
 07531                videoCodecs.RemoveAt(0);
 07532                videoCodecs.Add(removed);
 7533            }
 07534        }
 7535
 7536        private void NormalizeSubtitleEmbed(EncodingJobInfo state)
 7537        {
 07538            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7539            {
 07540                return;
 7541            }
 7542
 7543            // This is tricky to remux in, after converting to dvdsub it's not positioned correctly
 7544            // Therefore, let's just burn it in
 07545            if (string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 7546            {
 07547                state.SubtitleDeliveryMethod = SubtitleDeliveryMethod.Encode;
 7548            }
 07549        }
 7550
 7551        public string GetSubtitleEmbedArguments(EncodingJobInfo state)
 7552        {
 07553            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7554            {
 07555                return string.Empty;
 7556            }
 7557
 07558            var format = state.SupportedSubtitleCodecs.FirstOrDefault();
 7559            string codec;
 7560
 07561            if (string.IsNullOrEmpty(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.Ordin
 7562            {
 07563                codec = "copy";
 7564            }
 7565            else
 7566            {
 07567                codec = format;
 7568            }
 7569
 07570            return " -codec:s:0 " + codec + " -disposition:s:0 default";
 7571        }
 7572
 7573        public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, Encoder
 7574        {
 7575            // Get the output codec name
 07576            var videoCodec = GetVideoEncoder(state, encodingOptions);
 7577
 07578            var format = string.Empty;
 07579            var keyFrame = string.Empty;
 07580            var outputPath = state.OutputFilePath;
 7581
 07582            if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase)
 07583                && state.BaseRequest.Context == EncodingContext.Streaming)
 7584            {
 7585                // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
 07586                format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov";
 7587            }
 7588
 07589            var threads = GetNumberOfThreads(state, encodingOptions, videoCodec);
 7590
 07591            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7592
 07593            return string.Format(
 07594                CultureInfo.InvariantCulture,
 07595                "{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
 07596                inputModifier,
 07597                GetInputArgument(state, encodingOptions, null),
 07598                keyFrame,
 07599                GetMapArgs(state),
 07600                GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset),
 07601                threads,
 07602                GetProgressiveVideoAudioArguments(state, encodingOptions),
 07603                GetSubtitleEmbedArguments(state),
 07604                format,
 07605                outputPath).Trim();
 7606        }
 7607
 7608        public string GetOutputFFlags(EncodingJobInfo state)
 7609        {
 07610            var flags = new List<string>();
 07611            if (state.GenPtsOutput)
 7612            {
 07613                flags.Add("+genpts");
 7614            }
 7615
 07616            if (flags.Count > 0)
 7617            {
 07618                return " -fflags " + string.Join(string.Empty, flags);
 7619            }
 7620
 07621            return string.Empty;
 7622        }
 7623
 7624        public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoC
 7625        {
 07626            var args = "-codec:v:0 " + videoCodec;
 7627
 07628            if (state.BaseRequest.EnableMpegtsM2TsMode)
 7629            {
 07630                args += " -mpegts_m2ts_mode 1";
 7631            }
 7632
 07633            if (IsCopyCodec(videoCodec))
 7634            {
 07635                if (state.VideoStream is not null
 07636                    && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
 07637                    && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
 7638                {
 07639                    string bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Video);
 07640                    if (!string.IsNullOrEmpty(bitStreamArgs))
 7641                    {
 07642                        args += " " + bitStreamArgs;
 7643                    }
 7644                }
 7645
 07646                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7647                {
 07648                    args += " -copyts -avoid_negative_ts disabled -start_at_zero";
 7649                }
 7650
 07651                if (!state.RunTimeTicks.HasValue)
 7652                {
 07653                    args += " -fflags +genpts";
 7654                }
 7655            }
 7656            else
 7657            {
 07658                var keyFrameArg = string.Format(
 07659                    CultureInfo.InvariantCulture,
 07660                    " -force_key_frames \"expr:gte(t,n_forced*{0})\"",
 07661                    5);
 7662
 07663                args += keyFrameArg;
 7664
 07665                var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream &&
 7666
 07667                var hasCopyTs = false;
 7668
 7669                // video processing filters.
 07670                var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
 7671
 07672                var negativeMapArgs = GetNegativeMapArgsByFilters(state, videoProcessParam);
 7673
 07674                args = negativeMapArgs + args + videoProcessParam;
 7675
 07676                hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
 7677
 07678                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7679                {
 07680                    if (!hasCopyTs)
 7681                    {
 07682                        args += " -copyts";
 7683                    }
 7684
 07685                    args += " -avoid_negative_ts disabled";
 7686
 07687                    if (!(state.SubtitleStream is not null && state.SubtitleStream.IsExternal && !state.SubtitleStream.I
 7688                    {
 07689                        args += " -start_at_zero";
 7690                    }
 7691                }
 7692
 07693                var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset);
 7694
 07695                if (!string.IsNullOrEmpty(qualityParam))
 7696                {
 07697                    args += " " + qualityParam.Trim();
 7698                }
 7699            }
 7700
 07701            if (!string.IsNullOrEmpty(state.OutputVideoSync))
 7702            {
 07703                args += GetVideoSyncOption(state.OutputVideoSync, _mediaEncoder.EncoderVersion);
 7704            }
 7705
 07706            args += GetOutputFFlags(state);
 7707
 07708            return args;
 7709        }
 7710
 7711        public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions)
 7712        {
 7713            // If the video doesn't have an audio stream, return a default.
 07714            if (state.AudioStream is null && state.VideoStream is not null)
 7715            {
 07716                return string.Empty;
 7717            }
 7718
 7719            // Get the output codec name
 07720            var codec = GetAudioEncoder(state);
 7721
 07722            var args = "-codec:a:0 " + codec;
 7723
 07724            if (IsCopyCodec(codec))
 7725            {
 07726                return args;
 7727            }
 7728
 07729            var channels = state.OutputAudioChannels;
 7730
 07731            var useDownMixAlgorithm = state.AudioStream is not null
 07732                                      && DownMixAlgorithmsHelper.AlgorithmFilterStrings.ContainsKey((encodingOptions.Dow
 7733
 07734            if (channels.HasValue && !useDownMixAlgorithm)
 7735            {
 07736                args += " -ac " + channels.Value;
 7737            }
 7738
 07739            var bitrate = state.OutputAudioBitrate;
 07740            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
 7741            {
 07742                var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2);
 07743                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7744                {
 07745                    args += vbrParam;
 7746                }
 7747                else
 7748                {
 07749                    args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
 7750                }
 7751            }
 7752
 07753            if (state.OutputAudioSampleRate.HasValue)
 7754            {
 07755                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
 7756            }
 7757
 07758            args += GetAudioFilterParam(state, encodingOptions);
 7759
 07760            return args;
 7761        }
 7762
 7763        public string GetProgressiveAudioFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string 
 7764        {
 07765            var audioTranscodeParams = new List<string>();
 7766
 07767            var bitrate = state.OutputAudioBitrate;
 07768            var channels = state.OutputAudioChannels;
 07769            var outputCodec = state.OutputAudioCodec;
 7770
 07771            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
 7772            {
 07773                var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2);
 07774                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7775                {
 07776                    audioTranscodeParams.Add(vbrParam);
 7777                }
 7778                else
 7779                {
 07780                    audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
 7781                }
 7782            }
 7783
 07784            if (channels.HasValue)
 7785            {
 07786                audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)
 7787            }
 7788
 07789            if (!string.IsNullOrEmpty(outputCodec))
 7790            {
 07791                audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
 7792            }
 7793
 07794            if (GetAudioEncoder(state).StartsWith("pcm_", StringComparison.Ordinal))
 7795            {
 07796                audioTranscodeParams.Add(string.Concat("-f ", GetAudioEncoder(state).AsSpan(4)));
 07797                audioTranscodeParams.Add("-ar " + state.BaseRequest.AudioBitRate);
 7798            }
 7799
 07800            if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
 7801            {
 7802                // opus only supports specific sampling rates
 07803                var sampleRate = state.OutputAudioSampleRate;
 07804                if (sampleRate.HasValue)
 7805                {
 07806                    var sampleRateValue = sampleRate.Value switch
 07807                    {
 07808                        <= 8000 => 8000,
 07809                        <= 12000 => 12000,
 07810                        <= 16000 => 16000,
 07811                        <= 24000 => 24000,
 07812                        _ => 48000
 07813                    };
 7814
 07815                    audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
 7816                }
 7817            }
 7818
 7819            // Copy the movflags from GetProgressiveVideoFullCommandLine
 7820            // See #9248 and the associated PR for why this is needed
 07821            if (_mp4ContainerNames.Contains(state.OutputContainer))
 7822            {
 07823                audioTranscodeParams.Add("-movflags empty_moov+delay_moov");
 7824            }
 7825
 07826            var threads = GetNumberOfThreads(state, encodingOptions, null);
 7827
 07828            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7829
 07830            return string.Format(
 07831                CultureInfo.InvariantCulture,
 07832                "{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"",
 07833                inputModifier,
 07834                GetInputArgument(state, encodingOptions, null),
 07835                threads,
 07836                " -vn",
 07837                string.Join(' ', audioTranscodeParams),
 07838                outputPath,
 07839                string.Empty,
 07840                string.Empty,
 07841                string.Empty).Trim();
 7842        }
 7843
 7844        public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
 7845        {
 07846            var index = 0;
 07847            var length = mediaStreams.Count;
 7848
 07849            for (var i = 0; i < length; i++)
 7850            {
 07851                var currentMediaStream = mediaStreams[i];
 07852                if (currentMediaStream == streamToFind)
 7853                {
 07854                    return index;
 7855                }
 7856
 07857                if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
 7858                {
 07859                    index++;
 7860                }
 7861            }
 7862
 07863            return -1;
 7864        }
 7865
 7866        public static bool IsCopyCodec(string codec)
 7867        {
 07868            return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
 7869        }
 7870
 7871        private static bool ShouldEncodeSubtitle(EncodingJobInfo state)
 7872        {
 07873            return state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
 07874                   || (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec));
 7875        }
 7876
 7877        public static string GetVideoSyncOption(string videoSync, Version encoderVersion)
 7878        {
 07879            if (string.IsNullOrEmpty(videoSync))
 7880            {
 07881                return string.Empty;
 7882            }
 7883
 07884            if (encoderVersion >= new Version(5, 1))
 7885            {
 07886                if (int.TryParse(videoSync, CultureInfo.InvariantCulture, out var vsync))
 7887                {
 07888                    return vsync switch
 07889                    {
 07890                        -1 => " -fps_mode auto",
 07891                        0 => " -fps_mode passthrough",
 07892                        1 => " -fps_mode cfr",
 07893                        2 => " -fps_mode vfr",
 07894                        _ => string.Empty
 07895                    };
 7896                }
 7897
 07898                return string.Empty;
 7899            }
 7900
 7901            // -vsync is deprecated in FFmpeg 5.1 and will be removed in the future.
 07902            return $" -vsync {videoSync}";
 7903        }
 7904    }
 7905}

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)
GetVideoSyncOption(System.String,System.Version)