< 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: 26
Uncovered lines: 3659
Coverable lines: 3685
Total lines: 7704
Line coverage: 0.7%
Branch coverage
0%
Covered branches: 0
Total branches: 3657
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

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%272160%
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%210140%
IsVideoToolboxTonemapAvailable(...)0%110100%
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%1190340%
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%812280%
GetEncoderParam(...)0%8010890%
NormalizeTranscodingLevel(...)0%506220%
GetTextSubtitlesFilter(...)0%210140%
GetFramerateParam(...)0%156120%
GetHlsVideoKeyFrameArguments(...)0%1806420%
GetVideoQualityParam(...)0%325801800%
CanStreamCopyVideo(...)0%140421180%
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%702260%
GetHwTonemapFilter(...)0%1332360%
GetLibplaceboFilter(...)0%506220%
GetVideoTransposeDirection(...)0%156120%
GetSwVidFilterChain(...)0%2550500%
GetNvidiaVidFilterChain(...)0%110100%
GetNvidiaVidFiltersPrefered(...)0%7482860%
GetAmdVidFilterChain(...)0%210140%
GetAmdDx11VidFiltersPrefered(...)0%7140840%
GetIntelVidFilterChain(...)0%506220%
GetIntelQsvDx11VidFiltersPrefered(...)0%203061420%
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%131101140%
GetVideoProcessingFilterParam(...)0%2756520%
GetOverwriteColorPropertiesParam(...)0%2040%
GetInputHdrParam(...)0%620%
GetOutputSdrParam(...)0%2040%
GetVideoColorBitDepth(...)0%600240%
GetHardwareVideoDecoder(...)0%2352480%
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%272160%
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.
 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 ContainerValidationRegex = @"^[a-zA-Z0-9\-\._,|]{0,40}$";
 42
 43        /// <summary>
 44        /// The level validation regex.
 45        /// This regular expression matches strings representing a double.
 46        /// </summary>
 47        public const string LevelValidationRegex = @"-?[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);
 88
 089        private static readonly Regex _containerValidationRegex = new(ContainerValidationRegex, RegexOptions.Compiled);
 90
 091        private static readonly string[] _videoProfilesH264 =
 092        [
 093            "ConstrainedBaseline",
 094            "Baseline",
 095            "Extended",
 096            "Main",
 097            "High",
 098            "ProgressiveHigh",
 099            "ConstrainedHigh",
 0100            "High10"
 0101        ];
 102
 0103        private static readonly string[] _videoProfilesH265 =
 0104        [
 0105            "Main",
 0106            "Main10"
 0107        ];
 108
 0109        private static readonly string[] _videoProfilesAv1 =
 0110        [
 0111            "Main",
 0112            "High",
 0113            "Professional",
 0114        ];
 115
 0116        private static readonly HashSet<string> _mp4ContainerNames = new(StringComparer.OrdinalIgnoreCase)
 0117        {
 0118            "mp4",
 0119            "m4a",
 0120            "m4p",
 0121            "m4b",
 0122            "m4r",
 0123            "m4v",
 0124        };
 125
 0126        private static readonly TonemappingMode[] _legacyTonemapModes = [TonemappingMode.max, TonemappingMode.rgb];
 0127        private static readonly TonemappingMode[] _advancedTonemapModes = [TonemappingMode.lum, TonemappingMode.itp];
 128
 129        // Set max transcoding channels for encoders that can't handle more than a set amount of channels
 130        // AAC, FLAC, ALAC, libopus, libvorbis encoders all support at least 8 channels
 0131        private static readonly Dictionary<string, int> _audioTranscodeChannelLookup = new(StringComparer.OrdinalIgnoreC
 0132        {
 0133            { "libmp3lame", 2 },
 0134            { "libfdk_aac", 6 },
 0135            { "ac3", 6 },
 0136            { "eac3", 6 },
 0137            { "dca", 6 },
 0138            { "mlp", 6 },
 0139            { "truehd", 6 },
 0140        };
 141
 0142        private static readonly Dictionary<HardwareAccelerationType, string> _mjpegCodecMap = new()
 0143        {
 0144            { HardwareAccelerationType.vaapi, _defaultMjpegEncoder + "_vaapi" },
 0145            { HardwareAccelerationType.qsv, _defaultMjpegEncoder + "_qsv" },
 0146            { HardwareAccelerationType.videotoolbox, _defaultMjpegEncoder + "_videotoolbox" },
 0147            { HardwareAccelerationType.rkmpp, _defaultMjpegEncoder + "_rkmpp" }
 0148        };
 149
 0150        public static readonly string[] LosslessAudioCodecs =
 0151        [
 0152            "alac",
 0153            "ape",
 0154            "flac",
 0155            "mlp",
 0156            "truehd",
 0157            "wavpack"
 0158        ];
 159
 160        public EncodingHelper(
 161            IApplicationPaths appPaths,
 162            IMediaEncoder mediaEncoder,
 163            ISubtitleEncoder subtitleEncoder,
 164            IConfiguration config,
 165            IConfigurationManager configurationManager,
 166            IPathManager pathManager)
 167        {
 21168            _appPaths = appPaths;
 21169            _mediaEncoder = mediaEncoder;
 21170            _subtitleEncoder = subtitleEncoder;
 21171            _config = config;
 21172            _configurationManager = configurationManager;
 21173            _pathManager = pathManager;
 21174        }
 175
 176        private enum DynamicHdrMetadataRemovalPlan
 177        {
 178            None,
 179            RemoveDovi,
 180            RemoveHdr10Plus,
 181        }
 182
 183        [GeneratedRegex(@"\s+")]
 184        private static partial Regex WhiteSpaceRegex();
 185
 186        public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0187            => GetH26xOrAv1Encoder("libx264", "h264", state, encodingOptions);
 188
 189        public string GetH265Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0190            => GetH26xOrAv1Encoder("libx265", "hevc", state, encodingOptions);
 191
 192        public string GetAv1Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0193            => GetH26xOrAv1Encoder("libsvtav1", "av1", state, encodingOptions);
 194
 195        private string GetH26xOrAv1Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptio
 196        {
 197            // Only use alternative encoders for video files.
 198            // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying
 199            // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such 
 0200            if (state.VideoType == VideoType.VideoFile)
 201            {
 0202                var hwType = encodingOptions.HardwareAccelerationType;
 203
 0204                var codecMap = new Dictionary<HardwareAccelerationType, string>()
 0205                {
 0206                    { HardwareAccelerationType.amf,                  hwEncoder + "_amf" },
 0207                    { HardwareAccelerationType.nvenc,                hwEncoder + "_nvenc" },
 0208                    { HardwareAccelerationType.qsv,                  hwEncoder + "_qsv" },
 0209                    { HardwareAccelerationType.vaapi,                hwEncoder + "_vaapi" },
 0210                    { HardwareAccelerationType.videotoolbox,         hwEncoder + "_videotoolbox" },
 0211                    { HardwareAccelerationType.v4l2m2m,              hwEncoder + "_v4l2m2m" },
 0212                    { HardwareAccelerationType.rkmpp,                hwEncoder + "_rkmpp" },
 0213                };
 214
 0215                if (hwType != HardwareAccelerationType.none
 0216                    && encodingOptions.EnableHardwareEncoding
 0217                    && codecMap.TryGetValue(hwType, out var preferredEncoder)
 0218                    && _mediaEncoder.SupportsEncoder(preferredEncoder))
 219                {
 0220                    return preferredEncoder;
 221                }
 222            }
 223
 0224            return defaultEncoder;
 225        }
 226
 227        private string GetMjpegEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 228        {
 0229            if (state.VideoType == VideoType.VideoFile)
 230            {
 0231                var hwType = encodingOptions.HardwareAccelerationType;
 232
 233                // Only Intel has VA-API MJPEG encoder
 0234                if (hwType == HardwareAccelerationType.vaapi
 0235                    && !(_mediaEncoder.IsVaapiDeviceInteliHD
 0236                         || _mediaEncoder.IsVaapiDeviceInteli965))
 237                {
 0238                    return _defaultMjpegEncoder;
 239                }
 240
 0241                if (hwType != HardwareAccelerationType.none
 0242                    && encodingOptions.EnableHardwareEncoding
 0243                    && _mjpegCodecMap.TryGetValue(hwType, out var preferredEncoder)
 0244                    && _mediaEncoder.SupportsEncoder(preferredEncoder))
 245                {
 0246                    return preferredEncoder;
 247                }
 248            }
 249
 0250            return _defaultMjpegEncoder;
 251        }
 252
 253        private bool IsVaapiSupported(EncodingJobInfo state)
 254        {
 255            // vaapi will throw an error with this input
 256            // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99.
 0257            if (string.Equals(state.VideoStream?.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 258            {
 0259                return false;
 260            }
 261
 0262            return _mediaEncoder.SupportsHwaccel("vaapi");
 263        }
 264
 265        private bool IsVaapiFullSupported()
 266        {
 0267            return _mediaEncoder.SupportsHwaccel("drm")
 0268                   && _mediaEncoder.SupportsHwaccel("vaapi")
 0269                   && _mediaEncoder.SupportsFilter("scale_vaapi")
 0270                   && _mediaEncoder.SupportsFilter("deinterlace_vaapi")
 0271                   && _mediaEncoder.SupportsFilter("tonemap_vaapi")
 0272                   && _mediaEncoder.SupportsFilter("procamp_vaapi")
 0273                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVaapiFrameSync)
 0274                   && _mediaEncoder.SupportsFilter("transpose_vaapi")
 0275                   && _mediaEncoder.SupportsFilter("hwupload_vaapi");
 276        }
 277
 278        private bool IsRkmppFullSupported()
 279        {
 0280            return _mediaEncoder.SupportsHwaccel("rkmpp")
 0281                   && _mediaEncoder.SupportsFilter("scale_rkrga")
 0282                   && _mediaEncoder.SupportsFilter("vpp_rkrga")
 0283                   && _mediaEncoder.SupportsFilter("overlay_rkrga");
 284        }
 285
 286        private bool IsOpenclFullSupported()
 287        {
 0288            return _mediaEncoder.SupportsHwaccel("opencl")
 0289                   && _mediaEncoder.SupportsFilter("scale_opencl")
 0290                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390)
 0291                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclFrameSync);
 292
 293            // Let transpose_opencl optional for the time being.
 294        }
 295
 296        private bool IsCudaFullSupported()
 297        {
 0298            return _mediaEncoder.SupportsHwaccel("cuda")
 0299                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat)
 0300                   && _mediaEncoder.SupportsFilter("yadif_cuda")
 0301                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName)
 0302                   && _mediaEncoder.SupportsFilter("overlay_cuda")
 0303                   && _mediaEncoder.SupportsFilter("hwupload_cuda");
 304
 305            // Let transpose_cuda optional for the time being.
 306        }
 307
 308        private bool IsVulkanFullSupported()
 309        {
 0310            return _mediaEncoder.SupportsHwaccel("vulkan")
 0311                   && _mediaEncoder.SupportsFilter("libplacebo")
 0312                   && _mediaEncoder.SupportsFilter("scale_vulkan")
 0313                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVulkanFrameSync)
 0314                   && _mediaEncoder.SupportsFilter("transpose_vulkan")
 0315                   && _mediaEncoder.SupportsFilter("flip_vulkan");
 316        }
 317
 318        private bool IsVideoToolboxFullSupported()
 319        {
 0320            return _mediaEncoder.SupportsHwaccel("videotoolbox")
 0321                && _mediaEncoder.SupportsFilter("yadif_videotoolbox")
 0322                && _mediaEncoder.SupportsFilter("overlay_videotoolbox")
 0323                && _mediaEncoder.SupportsFilter("tonemap_videotoolbox")
 0324                && _mediaEncoder.SupportsFilter("scale_vt");
 325
 326            // Let transpose_vt optional for the time being.
 327        }
 328
 329        private bool IsSwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 330        {
 0331            if (state.VideoStream is null
 0332                || GetVideoColorBitDepth(state) < 10
 0333                || !_mediaEncoder.SupportsFilter("tonemapx"))
 334            {
 0335                return false;
 336            }
 337
 0338            return state.VideoStream.VideoRange == VideoRange.HDR;
 339        }
 340
 341        private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 342        {
 0343            if (state.VideoStream is null
 0344                || !options.EnableTonemapping
 0345                || GetVideoColorBitDepth(state) < 10)
 346            {
 0347                return false;
 348            }
 349
 0350            if (state.VideoStream.VideoRange == VideoRange.HDR
 0351                && state.VideoStream.VideoRangeType == VideoRangeType.DOVI)
 352            {
 353                // Only native SW decoder, HW accelerator and hevc_rkmpp decoder can parse dovi rpu.
 0354                var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 355
 0356                var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 0357                if (isRkmppDecoder
 0358                    && _mediaEncoder.EncoderVersion >= _minFFmpegRkmppHevcDecDoviRpu
 0359                    && string.Equals(state.VideoStream?.Codec, "hevc", StringComparison.OrdinalIgnoreCase))
 360                {
 0361                    return true;
 362                }
 363
 0364                var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 0365                var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 0366                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 0367                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 0368                var isVideoToolBoxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 0369                return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder || isVideoToolBoxDecoder;
 370            }
 371
 372            // GPU tonemapping supports all HDR RangeTypes
 0373            return state.VideoStream.VideoRange == VideoRange.HDR;
 374        }
 375
 376        private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 377        {
 0378            if (state.VideoStream is null)
 379            {
 0380                return false;
 381            }
 382
 383            // libplacebo has partial Dolby Vision to SDR tonemapping support.
 0384            return options.EnableTonemapping
 0385                   && state.VideoStream.VideoRange == VideoRange.HDR
 0386                   && GetVideoColorBitDepth(state) == 10;
 387        }
 388
 389        private bool IsIntelVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 390        {
 0391            if (state.VideoStream is null
 0392                || !options.EnableVppTonemapping
 0393                || GetVideoColorBitDepth(state) < 10)
 394            {
 0395                return false;
 396            }
 397
 398            // prefer 'tonemap_vaapi' over 'vpp_qsv' on Linux for supporting Gen9/KBLx.
 399            // 'vpp_qsv' requires VPL, which is only supported on Gen12/TGLx and newer.
 0400            if (OperatingSystem.IsWindows()
 0401                && options.HardwareAccelerationType == HardwareAccelerationType.qsv
 0402                && _mediaEncoder.EncoderVersion < _minFFmpegQsvVppTonemapOption)
 403            {
 0404                return false;
 405            }
 406
 0407            return state.VideoStream.VideoRange == VideoRange.HDR
 0408                   && IsDoviWithHdr10Bl(state.VideoStream);
 409        }
 410
 411        private bool IsVideoToolboxTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 412        {
 0413            if (state.VideoStream is null
 0414                || !options.EnableVideoToolboxTonemapping
 0415                || GetVideoColorBitDepth(state) < 10)
 416            {
 0417                return false;
 418            }
 419
 420            // Certain DV profile 5 video works in Safari with direct playing, but the VideoToolBox does not produce cor
 421            // All other HDR formats working.
 0422            return state.VideoStream.VideoRange == VideoRange.HDR
 0423                   && (IsDoviWithHdr10Bl(state.VideoStream)
 0424                       || state.VideoStream.VideoRangeType is VideoRangeType.HLG);
 425        }
 426
 427        private bool IsVideoStreamHevcRext(EncodingJobInfo state)
 428        {
 0429            var videoStream = state.VideoStream;
 0430            if (videoStream is null)
 431            {
 0432                return false;
 433            }
 434
 0435            return string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 0436                   && (string.Equals(videoStream.Profile, "Rext", StringComparison.OrdinalIgnoreCase)
 0437                       || string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 0438                       || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 0439                       || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 0440                       || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 0441                       || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase)
 0442                       || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)
 0443                       || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase));
 444        }
 445
 446        /// <summary>
 447        /// Gets the name of the output video codec.
 448        /// </summary>
 449        /// <param name="state">Encoding state.</param>
 450        /// <param name="encodingOptions">Encoding options.</param>
 451        /// <returns>Encoder string.</returns>
 452        public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 453        {
 0454            var codec = state.OutputVideoCodec;
 455
 0456            if (!string.IsNullOrEmpty(codec))
 457            {
 0458                if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 459                {
 0460                    return GetAv1Encoder(state, encodingOptions);
 461                }
 462
 0463                if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 0464                    || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
 465                {
 0466                    return GetH265Encoder(state, encodingOptions);
 467                }
 468
 0469                if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
 470                {
 0471                    return GetH264Encoder(state, encodingOptions);
 472                }
 473
 0474                if (string.Equals(codec, "mjpeg", StringComparison.OrdinalIgnoreCase))
 475                {
 0476                    return GetMjpegEncoder(state, encodingOptions);
 477                }
 478
 0479                if (_containerValidationRegex.IsMatch(codec))
 480                {
 0481                    return codec.ToLowerInvariant();
 482                }
 483            }
 484
 0485            return "copy";
 486        }
 487
 488        /// <summary>
 489        /// Gets the user agent param.
 490        /// </summary>
 491        /// <param name="state">The state.</param>
 492        /// <returns>System.String.</returns>
 493        public string GetUserAgentParam(EncodingJobInfo state)
 494        {
 0495            if (state.RemoteHttpHeaders.TryGetValue("User-Agent", out string useragent))
 496            {
 0497                return "-user_agent \"" + useragent + "\"";
 498            }
 499
 0500            return string.Empty;
 501        }
 502
 503        /// <summary>
 504        /// Gets the referer param.
 505        /// </summary>
 506        /// <param name="state">The state.</param>
 507        /// <returns>System.String.</returns>
 508        public string GetRefererParam(EncodingJobInfo state)
 509        {
 0510            if (state.RemoteHttpHeaders.TryGetValue("Referer", out string referer))
 511            {
 0512                return "-referer \"" + referer + "\"";
 513            }
 514
 0515            return string.Empty;
 516        }
 517
 518        public static string GetInputFormat(string container)
 519        {
 0520            if (string.IsNullOrEmpty(container) || !_containerValidationRegex.IsMatch(container))
 521            {
 0522                return null;
 523            }
 524
 0525            container = container.Replace("mkv", "matroska", StringComparison.OrdinalIgnoreCase);
 526
 0527            if (string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
 528            {
 0529                return "mpegts";
 530            }
 531
 532            // For these need to find out the ffmpeg names
 0533            if (string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase))
 534            {
 0535                return null;
 536            }
 537
 0538            if (string.Equals(container, "wmv", StringComparison.OrdinalIgnoreCase))
 539            {
 0540                return null;
 541            }
 542
 0543            if (string.Equals(container, "mts", StringComparison.OrdinalIgnoreCase))
 544            {
 0545                return null;
 546            }
 547
 0548            if (string.Equals(container, "vob", StringComparison.OrdinalIgnoreCase))
 549            {
 0550                return null;
 551            }
 552
 0553            if (string.Equals(container, "mpg", StringComparison.OrdinalIgnoreCase))
 554            {
 0555                return null;
 556            }
 557
 0558            if (string.Equals(container, "mpeg", StringComparison.OrdinalIgnoreCase))
 559            {
 0560                return null;
 561            }
 562
 0563            if (string.Equals(container, "rec", StringComparison.OrdinalIgnoreCase))
 564            {
 0565                return null;
 566            }
 567
 0568            if (string.Equals(container, "dvr-ms", StringComparison.OrdinalIgnoreCase))
 569            {
 0570                return null;
 571            }
 572
 0573            if (string.Equals(container, "ogm", StringComparison.OrdinalIgnoreCase))
 574            {
 0575                return null;
 576            }
 577
 0578            if (string.Equals(container, "divx", StringComparison.OrdinalIgnoreCase))
 579            {
 0580                return null;
 581            }
 582
 0583            if (string.Equals(container, "tp", StringComparison.OrdinalIgnoreCase))
 584            {
 0585                return null;
 586            }
 587
 0588            if (string.Equals(container, "rmvb", StringComparison.OrdinalIgnoreCase))
 589            {
 0590                return null;
 591            }
 592
 0593            if (string.Equals(container, "rtp", StringComparison.OrdinalIgnoreCase))
 594            {
 0595                return null;
 596            }
 597
 598            // Seeing reported failures here, not sure yet if this is related to specifying input format
 0599            if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase))
 600            {
 0601                return null;
 602            }
 603
 604            // obviously don't do this for strm files
 0605            if (string.Equals(container, "strm", StringComparison.OrdinalIgnoreCase))
 606            {
 0607                return null;
 608            }
 609
 610            // ISO files don't have an ffmpeg format
 0611            if (string.Equals(container, "iso", StringComparison.OrdinalIgnoreCase))
 612            {
 0613                return null;
 614            }
 615
 0616            return container;
 617        }
 618
 619        /// <summary>
 620        /// Gets decoder from a codec.
 621        /// </summary>
 622        /// <param name="codec">Codec to use.</param>
 623        /// <returns>Decoder string.</returns>
 624        public string GetDecoderFromCodec(string codec)
 625        {
 626            // For these need to find out the ffmpeg names
 0627            if (string.Equals(codec, "mp2", StringComparison.OrdinalIgnoreCase))
 628            {
 0629                return null;
 630            }
 631
 0632            if (string.Equals(codec, "aac_latm", StringComparison.OrdinalIgnoreCase))
 633            {
 0634                return null;
 635            }
 636
 0637            if (string.Equals(codec, "eac3", StringComparison.OrdinalIgnoreCase))
 638            {
 0639                return null;
 640            }
 641
 0642            if (_mediaEncoder.SupportsDecoder(codec))
 643            {
 0644                return codec;
 645            }
 646
 0647            return null;
 648        }
 649
 650        /// <summary>
 651        /// Infers the audio codec based on the url.
 652        /// </summary>
 653        /// <param name="container">Container to use.</param>
 654        /// <returns>Codec string.</returns>
 655        public string InferAudioCodec(string container)
 656        {
 0657            if (string.IsNullOrWhiteSpace(container))
 658            {
 659                // this may not work, but if the client is that broken we cannot do anything better
 0660                return "aac";
 661            }
 662
 0663            var inferredCodec = container.ToLowerInvariant();
 664
 0665            return inferredCodec switch
 0666            {
 0667                "ogg" or "oga" or "ogv" or "webm" or "webma" => "opus",
 0668                "m4a" or "m4b" or "mp4" or "mov" or "mkv" or "mka" => "aac",
 0669                "ts" or "avi" or "flv" or "f4v" or "swf" => "mp3",
 0670                _ => inferredCodec
 0671            };
 672        }
 673
 674        /// <summary>
 675        /// Infers the video codec.
 676        /// </summary>
 677        /// <param name="url">The URL.</param>
 678        /// <returns>System.Nullable{VideoCodecs}.</returns>
 679        public string InferVideoCodec(string url)
 680        {
 0681            var ext = Path.GetExtension(url.AsSpan());
 682
 0683            if (ext.Equals(".asf", StringComparison.OrdinalIgnoreCase))
 684            {
 0685                return "wmv";
 686            }
 687
 0688            if (ext.Equals(".webm", StringComparison.OrdinalIgnoreCase))
 689            {
 690                // TODO: this may not always mean VP8, as the codec ages
 0691                return "vp8";
 692            }
 693
 0694            if (ext.Equals(".ogg", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ogv", StringComparison.OrdinalIgn
 695            {
 0696                return "theora";
 697            }
 698
 0699            if (ext.Equals(".m3u8", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ts", StringComparison.OrdinalIgn
 700            {
 0701                return "h264";
 702            }
 703
 0704            return "copy";
 705        }
 706
 707        public int GetVideoProfileScore(string videoCodec, string videoProfile)
 708        {
 709            // strip spaces because they may be stripped out on the query string
 0710            string profile = videoProfile.Replace(" ", string.Empty, StringComparison.Ordinal);
 0711            if (string.Equals("h264", videoCodec, StringComparison.OrdinalIgnoreCase))
 712            {
 0713                return Array.FindIndex(_videoProfilesH264, x => string.Equals(x, profile, StringComparison.OrdinalIgnore
 714            }
 715
 0716            if (string.Equals("hevc", videoCodec, StringComparison.OrdinalIgnoreCase))
 717            {
 0718                return Array.FindIndex(_videoProfilesH265, x => string.Equals(x, profile, StringComparison.OrdinalIgnore
 719            }
 720
 0721            if (string.Equals("av1", videoCodec, StringComparison.OrdinalIgnoreCase))
 722            {
 0723                return Array.FindIndex(_videoProfilesAv1, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreC
 724            }
 725
 0726            return -1;
 727        }
 728
 729        /// <summary>
 730        /// Gets the audio encoder.
 731        /// </summary>
 732        /// <param name="state">The state.</param>
 733        /// <returns>System.String.</returns>
 734        public string GetAudioEncoder(EncodingJobInfo state)
 735        {
 0736            var codec = state.OutputAudioCodec;
 737
 0738            if (!_containerValidationRegex.IsMatch(codec))
 739            {
 0740                codec = "aac";
 741            }
 742
 0743            if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
 744            {
 745                // Use Apple's aac encoder if available as it provides best audio quality
 0746                if (_mediaEncoder.SupportsEncoder("aac_at"))
 747                {
 0748                    return "aac_at";
 749                }
 750
 751                // Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
 0752                if (_mediaEncoder.SupportsEncoder("libfdk_aac"))
 753                {
 0754                    return "libfdk_aac";
 755                }
 756
 0757                return "aac";
 758            }
 759
 0760            if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
 761            {
 0762                return "libmp3lame";
 763            }
 764
 0765            if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase))
 766            {
 0767                return "libvorbis";
 768            }
 769
 0770            if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
 771            {
 0772                return "libopus";
 773            }
 774
 0775            if (string.Equals(codec, "flac", StringComparison.OrdinalIgnoreCase))
 776            {
 0777                return "flac";
 778            }
 779
 0780            if (string.Equals(codec, "dts", StringComparison.OrdinalIgnoreCase))
 781            {
 0782                return "dca";
 783            }
 784
 0785            if (string.Equals(codec, "alac", StringComparison.OrdinalIgnoreCase))
 786            {
 787                // The ffmpeg upstream breaks the AudioToolbox ALAC encoder in version 6.1 but fixes it in version 7.0.
 788                // Since ALAC is lossless in quality and the AudioToolbox encoder is not faster,
 789                // its only benefit is a smaller file size.
 790                // To prevent problems, use the ffmpeg native encoder instead.
 0791                return "alac";
 792            }
 793
 0794            return codec.ToLowerInvariant();
 795        }
 796
 797        private string GetRkmppDeviceArgs(string alias)
 798        {
 0799            alias ??= RkmppAlias;
 800
 801            // device selection in rk is not supported.
 0802            return " -init_hw_device rkmpp=" + alias;
 803        }
 804
 805        private string GetVideoToolboxDeviceArgs(string alias)
 806        {
 0807            alias ??= VideotoolboxAlias;
 808
 809            // device selection in vt is not supported.
 0810            return " -init_hw_device videotoolbox=" + alias;
 811        }
 812
 813        private string GetCudaDeviceArgs(int deviceIndex, string alias)
 814        {
 0815            alias ??= CudaAlias;
 0816            deviceIndex = deviceIndex >= 0
 0817                ? deviceIndex
 0818                : 0;
 819
 0820            return string.Format(
 0821                CultureInfo.InvariantCulture,
 0822                " -init_hw_device cuda={0}:{1}",
 0823                alias,
 0824                deviceIndex);
 825        }
 826
 827        private string GetVulkanDeviceArgs(int deviceIndex, string deviceName, string srcDeviceAlias, string alias)
 828        {
 0829            alias ??= VulkanAlias;
 0830            deviceIndex = deviceIndex >= 0
 0831                ? deviceIndex
 0832                : 0;
 0833            var vendorOpts = string.IsNullOrEmpty(deviceName)
 0834                ? ":" + deviceIndex
 0835                : ":" + "\"" + deviceName + "\"";
 0836            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0837                ? vendorOpts
 0838                : "@" + srcDeviceAlias;
 839
 0840            return string.Format(
 0841                CultureInfo.InvariantCulture,
 0842                " -init_hw_device vulkan={0}{1}",
 0843                alias,
 0844                options);
 845        }
 846
 847        private string GetOpenclDeviceArgs(int deviceIndex, string deviceVendorName, string srcDeviceAlias, string alias
 848        {
 0849            alias ??= OpenclAlias;
 0850            deviceIndex = deviceIndex >= 0
 0851                ? deviceIndex
 0852                : 0;
 0853            var vendorOpts = string.IsNullOrEmpty(deviceVendorName)
 0854                ? ":0.0"
 0855                : ":." + deviceIndex + ",device_vendor=\"" + deviceVendorName + "\"";
 0856            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0857                ? vendorOpts
 0858                : "@" + srcDeviceAlias;
 859
 0860            return string.Format(
 0861                CultureInfo.InvariantCulture,
 0862                " -init_hw_device opencl={0}{1}",
 0863                alias,
 0864                options);
 865        }
 866
 867        private string GetD3d11vaDeviceArgs(int deviceIndex, string deviceVendorId, string alias)
 868        {
 0869            alias ??= D3d11vaAlias;
 0870            deviceIndex = deviceIndex >= 0 ? deviceIndex : 0;
 0871            var options = string.IsNullOrEmpty(deviceVendorId)
 0872                ? deviceIndex.ToString(CultureInfo.InvariantCulture)
 0873                : ",vendor=" + deviceVendorId;
 874
 0875            return string.Format(
 0876                CultureInfo.InvariantCulture,
 0877                " -init_hw_device d3d11va={0}:{1}",
 0878                alias,
 0879                options);
 880        }
 881
 882        private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string vendorId, st
 883        {
 0884            alias ??= VaapiAlias;
 0885            var haveVendorId = !string.IsNullOrEmpty(vendorId)
 0886                && _mediaEncoder.EncoderVersion >= _minFFmpegVaapiDeviceVendorId;
 887
 888            // Priority: 'renderNodePath' > 'vendorId' > 'kernelDriver'
 0889            var driverOpts = File.Exists(renderNodePath)
 0890                ? renderNodePath
 0891                : (haveVendorId ? $",vendor_id={vendorId}" : (string.IsNullOrEmpty(kernelDriver) ? string.Empty : $",ker
 892
 893            // 'driver' behaves similarly to env LIBVA_DRIVER_NAME
 0894            driverOpts += string.IsNullOrEmpty(driver) ? string.Empty : ",driver=" + driver;
 895
 0896            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0897                ? (string.IsNullOrEmpty(driverOpts) ? string.Empty : ":" + driverOpts)
 0898                : "@" + srcDeviceAlias;
 899
 0900            return string.Format(
 0901                CultureInfo.InvariantCulture,
 0902                " -init_hw_device vaapi={0}{1}",
 0903                alias,
 0904                options);
 905        }
 906
 907        private string GetDrmDeviceArgs(string renderNodePath, string alias)
 908        {
 0909            alias ??= DrmAlias;
 0910            renderNodePath = renderNodePath ?? "/dev/dri/renderD128";
 911
 0912            return string.Format(
 0913                CultureInfo.InvariantCulture,
 0914                " -init_hw_device drm={0}:{1}",
 0915                alias,
 0916                renderNodePath);
 917        }
 918
 919        private string GetQsvDeviceArgs(string renderNodePath, string alias)
 920        {
 0921            var arg = " -init_hw_device qsv=" + (alias ?? QsvAlias);
 0922            if (OperatingSystem.IsLinux())
 923            {
 924                // derive qsv from vaapi device
 0925                return GetVaapiDeviceArgs(renderNodePath, "iHD", "i915", "0x8086", null, VaapiAlias) + arg + "@" + Vaapi
 926            }
 927
 0928            if (OperatingSystem.IsWindows())
 929            {
 930                // on Windows, the deviceIndex is an int
 0931                if (int.TryParse(renderNodePath, NumberStyles.Integer, CultureInfo.InvariantCulture, out int deviceIndex
 932                {
 0933                    return GetD3d11vaDeviceArgs(deviceIndex, string.Empty, D3d11vaAlias) + arg + "@" + D3d11vaAlias;
 934                }
 935
 936                // derive qsv from d3d11va device
 0937                return GetD3d11vaDeviceArgs(0, "0x8086", D3d11vaAlias) + arg + "@" + D3d11vaAlias;
 938            }
 939
 0940            return null;
 941        }
 942
 943        private string GetFilterHwDeviceArgs(string alias)
 944        {
 0945            return string.IsNullOrEmpty(alias)
 0946                ? string.Empty
 0947                : " -filter_hw_device " + alias;
 948        }
 949
 950        public string GetGraphicalSubCanvasSize(EncodingJobInfo state)
 951        {
 952            // DVBSUB uses the fixed canvas size 720x576
 0953            if (state.SubtitleStream is not null
 0954                && ShouldEncodeSubtitle(state)
 0955                && !state.SubtitleStream.IsTextSubtitleStream
 0956                && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 957            {
 0958                var subtitleWidth = state.SubtitleStream?.Width;
 0959                var subtitleHeight = state.SubtitleStream?.Height;
 960
 0961                if (subtitleWidth.HasValue
 0962                    && subtitleHeight.HasValue
 0963                    && subtitleWidth.Value > 0
 0964                    && subtitleHeight.Value > 0)
 965                {
 0966                    return string.Format(
 0967                        CultureInfo.InvariantCulture,
 0968                        " -canvas_size {0}x{1}",
 0969                        subtitleWidth.Value,
 0970                        subtitleHeight.Value);
 971                }
 972            }
 973
 0974            return string.Empty;
 975        }
 976
 977        /// <summary>
 978        /// Gets the input video hwaccel argument.
 979        /// </summary>
 980        /// <param name="state">Encoding state.</param>
 981        /// <param name="options">Encoding options.</param>
 982        /// <returns>Input video hwaccel arguments.</returns>
 983        public string GetInputVideoHwaccelArgs(EncodingJobInfo state, EncodingOptions options)
 984        {
 0985            if (!state.IsVideoRequest)
 986            {
 0987                return string.Empty;
 988            }
 989
 0990            var vidEncoder = GetVideoEncoder(state, options) ?? string.Empty;
 0991            if (IsCopyCodec(vidEncoder))
 992            {
 0993                return string.Empty;
 994            }
 995
 0996            var args = new StringBuilder();
 0997            var isWindows = OperatingSystem.IsWindows();
 0998            var isLinux = OperatingSystem.IsLinux();
 0999            var isMacOS = OperatingSystem.IsMacOS();
 01000            var optHwaccelType = options.HardwareAccelerationType;
 01001            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 01002            var isHwTonemapAvailable = IsHwTonemapAvailable(state, options);
 1003
 01004            if (optHwaccelType == HardwareAccelerationType.vaapi)
 1005            {
 01006                if (!isLinux || !_mediaEncoder.SupportsHwaccel("vaapi"))
 1007                {
 01008                    return string.Empty;
 1009                }
 1010
 01011                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01012                var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01013                if (!isVaapiDecoder && !isVaapiEncoder)
 1014                {
 01015                    return string.Empty;
 1016                }
 1017
 01018                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 1019                {
 01020                    args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "iHD", null, null, null, VaapiAlias));
 1021                }
 01022                else if (_mediaEncoder.IsVaapiDeviceInteli965)
 1023                {
 1024                    // Only override i965 since it has lower priority than iHD in libva lookup.
 01025                    Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME", "i965");
 01026                    Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME_JELLYFIN", "i965");
 01027                    args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "i965", null, null, null, VaapiAlias));
 1028                }
 1029
 01030                var filterDevArgs = string.Empty;
 01031                var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
 1032
 01033                if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 1034                {
 01035                    if (doOclTonemap && !isVaapiDecoder)
 1036                    {
 01037                        args.Append(GetOpenclDeviceArgs(0, null, VaapiAlias, OpenclAlias));
 01038                        filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1039                    }
 1040                }
 01041                else if (_mediaEncoder.IsVaapiDeviceAmd)
 1042                {
 1043                    // Disable AMD EFC feature since it's still unstable in upstream Mesa.
 01044                    Environment.SetEnvironmentVariable("AMD_DEBUG", "noefc");
 1045
 01046                    if (IsVulkanFullSupported()
 01047                        && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 01048                        && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 1049                    {
 01050                        args.Append(GetDrmDeviceArgs(options.VaapiDevice, DrmAlias));
 01051                        args.Append(GetVaapiDeviceArgs(null, null, null, null, DrmAlias, VaapiAlias));
 01052                        args.Append(GetVulkanDeviceArgs(0, null, DrmAlias, VulkanAlias));
 1053
 1054                        // libplacebo wants an explicitly set vulkan filter device.
 01055                        filterDevArgs = GetFilterHwDeviceArgs(VulkanAlias);
 1056                    }
 1057                    else
 1058                    {
 01059                        args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, null, null, VaapiAlias));
 01060                        filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias);
 1061
 01062                        if (doOclTonemap)
 1063                        {
 1064                            // ROCm/ROCr OpenCL runtime
 01065                            args.Append(GetOpenclDeviceArgs(0, "Advanced Micro Devices", null, OpenclAlias));
 01066                            filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1067                        }
 1068                    }
 1069                }
 01070                else if (doOclTonemap)
 1071                {
 01072                    args.Append(GetOpenclDeviceArgs(0, null, null, OpenclAlias));
 01073                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1074                }
 1075
 01076                args.Append(filterDevArgs);
 1077            }
 01078            else if (optHwaccelType == HardwareAccelerationType.qsv)
 1079            {
 01080                if ((!isLinux && !isWindows) || !_mediaEncoder.SupportsHwaccel("qsv"))
 1081                {
 01082                    return string.Empty;
 1083                }
 1084
 01085                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 01086                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01087                var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 01088                var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 01089                var isHwDecoder = isQsvDecoder || isVaapiDecoder || isD3d11vaDecoder;
 01090                if (!isHwDecoder && !isQsvEncoder)
 1091                {
 01092                    return string.Empty;
 1093                }
 1094
 01095                args.Append(GetQsvDeviceArgs(options.QsvDevice, QsvAlias));
 01096                var filterDevArgs = GetFilterHwDeviceArgs(QsvAlias);
 1097                // child device used by qsv.
 01098                if (_mediaEncoder.SupportsHwaccel("vaapi") || _mediaEncoder.SupportsHwaccel("d3d11va"))
 1099                {
 01100                    if (isHwTonemapAvailable && IsOpenclFullSupported())
 1101                    {
 01102                        var srcAlias = isLinux ? VaapiAlias : D3d11vaAlias;
 01103                        args.Append(GetOpenclDeviceArgs(0, null, srcAlias, OpenclAlias));
 01104                        if (!isHwDecoder)
 1105                        {
 01106                            filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1107                        }
 1108                    }
 1109                }
 1110
 01111                args.Append(filterDevArgs);
 1112            }
 01113            else if (optHwaccelType == HardwareAccelerationType.nvenc)
 1114            {
 01115                if ((!isLinux && !isWindows) || !IsCudaFullSupported())
 1116                {
 01117                    return string.Empty;
 1118                }
 1119
 01120                var isCuvidDecoder = vidDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase);
 01121                var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 01122                var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 01123                var isHwDecoder = isNvdecDecoder || isCuvidDecoder;
 01124                if (!isHwDecoder && !isNvencEncoder)
 1125                {
 01126                    return string.Empty;
 1127                }
 1128
 01129                args.Append(GetCudaDeviceArgs(0, CudaAlias))
 01130                     .Append(GetFilterHwDeviceArgs(CudaAlias));
 1131            }
 01132            else if (optHwaccelType == HardwareAccelerationType.amf)
 1133            {
 01134                if (!isWindows || !_mediaEncoder.SupportsHwaccel("d3d11va"))
 1135                {
 01136                    return string.Empty;
 1137                }
 1138
 01139                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 01140                var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 01141                if (!isD3d11vaDecoder && !isAmfEncoder)
 1142                {
 01143                    return string.Empty;
 1144                }
 1145
 1146                // no dxva video processor hw filter.
 01147                args.Append(GetD3d11vaDeviceArgs(0, "0x1002", D3d11vaAlias));
 01148                var filterDevArgs = string.Empty;
 01149                if (IsOpenclFullSupported())
 1150                {
 01151                    args.Append(GetOpenclDeviceArgs(0, null, D3d11vaAlias, OpenclAlias));
 01152                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1153                }
 1154
 01155                args.Append(filterDevArgs);
 1156            }
 01157            else if (optHwaccelType == HardwareAccelerationType.videotoolbox)
 1158            {
 01159                if (!isMacOS || !_mediaEncoder.SupportsHwaccel("videotoolbox"))
 1160                {
 01161                    return string.Empty;
 1162                }
 1163
 01164                var isVideotoolboxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 01165                var isVideotoolboxEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 01166                if (!isVideotoolboxDecoder && !isVideotoolboxEncoder)
 1167                {
 01168                    return string.Empty;
 1169                }
 1170
 1171                // videotoolbox hw filter does not require device selection
 01172                args.Append(GetVideoToolboxDeviceArgs(VideotoolboxAlias));
 1173            }
 01174            else if (optHwaccelType == HardwareAccelerationType.rkmpp)
 1175            {
 01176                if (!isLinux || !_mediaEncoder.SupportsHwaccel("rkmpp"))
 1177                {
 01178                    return string.Empty;
 1179                }
 1180
 01181                var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 01182                var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 01183                if (!isRkmppDecoder && !isRkmppEncoder)
 1184                {
 01185                    return string.Empty;
 1186                }
 1187
 01188                args.Append(GetRkmppDeviceArgs(RkmppAlias));
 1189
 01190                var filterDevArgs = string.Empty;
 01191                var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
 1192
 01193                if (doOclTonemap && !isRkmppDecoder)
 1194                {
 01195                    args.Append(GetOpenclDeviceArgs(0, null, RkmppAlias, OpenclAlias));
 01196                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1197                }
 1198
 01199                args.Append(filterDevArgs);
 1200            }
 1201
 01202            if (!string.IsNullOrEmpty(vidDecoder))
 1203            {
 01204                args.Append(vidDecoder);
 1205            }
 1206
 01207            return args.ToString().Trim();
 1208        }
 1209
 1210        /// <summary>
 1211        /// Gets the input argument.
 1212        /// </summary>
 1213        /// <param name="state">Encoding state.</param>
 1214        /// <param name="options">Encoding options.</param>
 1215        /// <param name="segmentContainer">Segment Container.</param>
 1216        /// <returns>Input arguments.</returns>
 1217        public string GetInputArgument(EncodingJobInfo state, EncodingOptions options, string segmentContainer)
 1218        {
 01219            var arg = new StringBuilder();
 01220            var inputVidHwaccelArgs = GetInputVideoHwaccelArgs(state, options);
 1221
 01222            if (!string.IsNullOrEmpty(inputVidHwaccelArgs))
 1223            {
 01224                arg.Append(inputVidHwaccelArgs);
 1225            }
 1226
 01227            var canvasArgs = GetGraphicalSubCanvasSize(state);
 01228            if (!string.IsNullOrEmpty(canvasArgs))
 1229            {
 01230                arg.Append(canvasArgs);
 1231            }
 1232
 01233            if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
 1234            {
 01235                var concatFilePath = Path.Join(_configurationManager.CommonApplicationPaths.CachePath, "concat", state.M
 01236                if (!File.Exists(concatFilePath))
 1237                {
 01238                    _mediaEncoder.GenerateConcatConfig(state.MediaSource, concatFilePath);
 1239                }
 1240
 01241                arg.Append(" -f concat -safe 0 -i \"")
 01242                    .Append(concatFilePath)
 01243                    .Append("\" ");
 1244            }
 1245            else
 1246            {
 01247                arg.Append(" -i ")
 01248                    .Append(_mediaEncoder.GetInputPathArgument(state));
 1249            }
 1250
 1251            // sub2video for external graphical subtitles
 01252            if (state.SubtitleStream is not null
 01253                && ShouldEncodeSubtitle(state)
 01254                && !state.SubtitleStream.IsTextSubtitleStream
 01255                && state.SubtitleStream.IsExternal)
 1256            {
 01257                var subtitlePath = state.SubtitleStream.Path;
 01258                var subtitleExtension = Path.GetExtension(subtitlePath.AsSpan());
 1259
 1260                // dvdsub/vobsub graphical subtitles use .sub+.idx pairs
 01261                if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase))
 1262                {
 01263                    var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
 01264                    if (File.Exists(idxFile))
 1265                    {
 01266                        subtitlePath = idxFile;
 1267                    }
 1268                }
 1269
 1270                // Also seek the external subtitles stream.
 01271                var seekSubParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01272                if (!string.IsNullOrEmpty(seekSubParam))
 1273                {
 01274                    arg.Append(' ').Append(seekSubParam);
 1275                }
 1276
 01277                if (!string.IsNullOrEmpty(canvasArgs))
 1278                {
 01279                    arg.Append(canvasArgs);
 1280                }
 1281
 01282                arg.Append(" -i file:\"").Append(subtitlePath).Append('\"');
 1283            }
 1284
 01285            if (state.AudioStream is not null && state.AudioStream.IsExternal)
 1286            {
 1287                // Also seek the external audio stream.
 01288                var seekAudioParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01289                if (!string.IsNullOrEmpty(seekAudioParam))
 1290                {
 01291                    arg.Append(' ').Append(seekAudioParam);
 1292                }
 1293
 01294                arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
 1295            }
 1296
 1297            // Disable auto inserted SW scaler for HW decoders in case of changed resolution.
 01298            var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
 01299            if (!isSwDecoder)
 1300            {
 01301                arg.Append(" -noautoscale");
 1302            }
 1303
 01304            return arg.ToString();
 1305        }
 1306
 1307        /// <summary>
 1308        /// Determines whether the specified stream is H264.
 1309        /// </summary>
 1310        /// <param name="stream">The stream.</param>
 1311        /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
 1312        public static bool IsH264(MediaStream stream)
 1313        {
 01314            var codec = stream.Codec ?? string.Empty;
 1315
 01316            return codec.Contains("264", StringComparison.OrdinalIgnoreCase)
 01317                    || codec.Contains("avc", StringComparison.OrdinalIgnoreCase);
 1318        }
 1319
 1320        public static bool IsH265(MediaStream stream)
 1321        {
 01322            var codec = stream.Codec ?? string.Empty;
 1323
 01324            return codec.Contains("265", StringComparison.OrdinalIgnoreCase)
 01325                || codec.Contains("hevc", StringComparison.OrdinalIgnoreCase);
 1326        }
 1327
 1328        public static bool IsAv1(MediaStream stream)
 1329        {
 01330            var codec = stream.Codec ?? string.Empty;
 1331
 01332            return codec.Contains("av1", StringComparison.OrdinalIgnoreCase);
 1333        }
 1334
 1335        public static bool IsAAC(MediaStream stream)
 1336        {
 01337            var codec = stream.Codec ?? string.Empty;
 1338
 01339            return codec.Contains("aac", StringComparison.OrdinalIgnoreCase);
 1340        }
 1341
 1342        public static bool IsDoviWithHdr10Bl(MediaStream stream)
 1343        {
 01344            var rangeType = stream?.VideoRangeType;
 1345
 01346            return rangeType is VideoRangeType.DOVIWithHDR10
 01347                or VideoRangeType.DOVIWithEL
 01348                or VideoRangeType.DOVIWithHDR10Plus
 01349                or VideoRangeType.DOVIWithELHDR10Plus
 01350                or VideoRangeType.DOVIInvalid;
 1351        }
 1352
 1353        public static bool IsDovi(MediaStream stream)
 1354        {
 01355            var rangeType = stream?.VideoRangeType;
 1356
 01357            return IsDoviWithHdr10Bl(stream)
 01358                   || (rangeType is VideoRangeType.DOVI
 01359                       or VideoRangeType.DOVIWithHLG
 01360                       or VideoRangeType.DOVIWithSDR);
 1361        }
 1362
 1363        public static bool IsHdr10Plus(MediaStream stream)
 1364        {
 01365            var rangeType = stream?.VideoRangeType;
 1366
 01367            return rangeType is VideoRangeType.HDR10Plus
 01368                       or VideoRangeType.DOVIWithHDR10Plus
 01369                       or VideoRangeType.DOVIWithELHDR10Plus;
 1370        }
 1371
 1372        /// <summary>
 1373        /// Check if dynamic HDR metadata should be removed during stream copy.
 1374        /// Please note this check assumes the range check has already been done
 1375        /// and trivial fallbacks like HDR10+ to HDR10, DOVIWithHDR10 to HDR10 is already checked.
 1376        /// </summary>
 1377        private static DynamicHdrMetadataRemovalPlan ShouldRemoveDynamicHdrMetadata(EncodingJobInfo state)
 1378        {
 01379            var videoStream = state.VideoStream;
 01380            if (videoStream.VideoRange is not VideoRange.HDR)
 1381            {
 01382                return DynamicHdrMetadataRemovalPlan.None;
 1383            }
 1384
 01385            var requestedRangeTypes = state.GetRequestedRangeTypes(state.VideoStream.Codec);
 01386            if (requestedRangeTypes.Length == 0)
 1387            {
 01388                return DynamicHdrMetadataRemovalPlan.None;
 1389            }
 1390
 01391            var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ordinal
 01392            var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIg
 01393            var requestHasDOVIwithEL = requestedRangeTypes.Contains(VideoRangeType.DOVIWithEL.ToString(), StringComparis
 01394            var requestHasDOVIwithELHDR10plus = requestedRangeTypes.Contains(VideoRangeType.DOVIWithELHDR10Plus.ToString
 1395
 01396            var shouldRemoveHdr10Plus = false;
 1397            // Case 1: Client supports HDR10, does not support DOVI with EL but EL presets
 01398            var shouldRemoveDovi = (!requestHasDOVIwithEL && requestHasHDR10) && videoStream.VideoRangeType == VideoRang
 1399
 1400            // Case 2: Client supports DOVI, does not support broken DOVI config
 1401            // Client does not report DOVI support should be allowed to copy bad data for remuxing as HDR10 players woul
 01402            shouldRemoveDovi = shouldRemoveDovi || (requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVII
 1403
 1404            // Special case: we have a video with both EL and HDR10+
 1405            // If the client supports EL but not in the case of coexistence with HDR10+, remove HDR10+ for compatibility
 1406            // Otherwise, remove DOVI if the client is not a DOVI player
 01407            if (videoStream.VideoRangeType == VideoRangeType.DOVIWithELHDR10Plus)
 1408            {
 01409                shouldRemoveHdr10Plus = requestHasDOVIwithEL && !requestHasDOVIwithELHDR10plus;
 01410                shouldRemoveDovi = shouldRemoveDovi || !shouldRemoveHdr10Plus;
 1411            }
 1412
 01413            if (shouldRemoveDovi)
 1414            {
 01415                return DynamicHdrMetadataRemovalPlan.RemoveDovi;
 1416            }
 1417
 1418            // If the client is a Dolby Vision Player, remove the HDR10+ metadata to avoid playback issues
 01419            shouldRemoveHdr10Plus = shouldRemoveHdr10Plus || (requestHasDOVI && videoStream.VideoRangeType == VideoRange
 01420            return shouldRemoveHdr10Plus ? DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus : DynamicHdrMetadataRemovalPlan
 1421        }
 1422
 1423        private bool CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan plan, MediaStream videoStream)
 1424        {
 01425            return plan switch
 01426            {
 01427                DynamicHdrMetadataRemovalPlan.RemoveDovi => _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFil
 01428                                                            || (IsH265(videoStream) && _mediaEncoder.SupportsBitStreamFi
 01429                                                            || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStreamFil
 01430                DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus => (IsH265(videoStream) && _mediaEncoder.SupportsBitStream
 01431                                                                 || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStre
 01432                _ => true,
 01433            };
 1434        }
 1435
 1436        public bool IsDoviRemoved(EncodingJobInfo state)
 1437        {
 01438            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01439                                              && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Remove
 1440        }
 1441
 1442        public bool IsHdr10PlusRemoved(EncodingJobInfo state)
 1443        {
 01444            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01445                                                  && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Re
 1446        }
 1447
 1448        public string GetBitStreamArgs(EncodingJobInfo state, MediaStreamType streamType)
 1449        {
 01450            if (state is null)
 1451            {
 01452                return null;
 1453            }
 1454
 01455            var stream = streamType switch
 01456            {
 01457                MediaStreamType.Audio => state.AudioStream,
 01458                MediaStreamType.Video => state.VideoStream,
 01459                _ => state.VideoStream
 01460            };
 1461            // TODO This is auto inserted into the mpegts mux so it might not be needed.
 1462            // https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
 01463            if (IsH264(stream))
 1464            {
 01465                return "-bsf:v h264_mp4toannexb";
 1466            }
 1467
 01468            if (IsAAC(stream))
 1469            {
 1470                // Convert adts header(mpegts) to asc header(mp4).
 01471                return "-bsf:a aac_adtstoasc";
 1472            }
 1473
 01474            if (IsH265(stream))
 1475            {
 01476                var filter = "-bsf:v hevc_mp4toannexb";
 1477
 1478                // The following checks are not complete because the copy would be rejected
 1479                // if the encoder cannot remove required metadata.
 1480                // And if bsf is used, we must already be using copy codec.
 01481                switch (ShouldRemoveDynamicHdrMetadata(state))
 1482                {
 1483                    default:
 1484                    case DynamicHdrMetadataRemovalPlan.None:
 1485                        break;
 1486                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01487                        filter += _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.HevcMetadata
 01488                            ? ",hevc_metadata=remove_dovi=1"
 01489                            : ",dovi_rpu=strip=1";
 01490                        break;
 1491                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01492                        filter += ",hevc_metadata=remove_hdr10plus=1";
 1493                        break;
 1494                }
 1495
 01496                return filter;
 1497            }
 1498
 01499            if (IsAv1(stream))
 1500            {
 01501                switch (ShouldRemoveDynamicHdrMetadata(state))
 1502                {
 1503                    default:
 1504                    case DynamicHdrMetadataRemovalPlan.None:
 01505                        return null;
 1506                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01507                        return _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.Av1MetadataRemo
 01508                            ? "-bsf:v av1_metadata=remove_dovi=1"
 01509                            : "-bsf:v dovi_rpu=strip=1";
 1510                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01511                        return "-bsf:v av1_metadata=remove_hdr10plus=1";
 1512                }
 1513            }
 1514
 01515            return null;
 1516        }
 1517
 1518        public string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceConta
 1519        {
 01520            var bitStreamArgs = string.Empty;
 01521            var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
 1522
 1523            // Apply aac_adtstoasc bitstream filter when media source is in mpegts.
 01524            if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)
 01525                && (string.Equals(mediaSourceContainer, "ts", StringComparison.OrdinalIgnoreCase)
 01526                    || string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase)
 01527                    || string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase)))
 1528            {
 01529                bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Audio);
 01530                bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs;
 1531            }
 1532
 01533            return bitStreamArgs;
 1534        }
 1535
 1536        public static string GetSegmentFileExtension(string segmentContainer)
 1537        {
 01538            if (!string.IsNullOrWhiteSpace(segmentContainer))
 1539            {
 01540                return "." + segmentContainer;
 1541            }
 1542
 01543            return ".ts";
 1544        }
 1545
 1546        private string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
 1547        {
 01548            if (state.OutputVideoBitrate is null)
 1549            {
 01550                return string.Empty;
 1551            }
 1552
 01553            int bitrate = state.OutputVideoBitrate.Value;
 1554
 1555            // Bit rate under 1000k is not allowed in h264_qsv
 01556            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 1557            {
 01558                bitrate = Math.Max(bitrate, 1000);
 1559            }
 1560
 1561            // Currently use the same buffer size for all encoders
 01562            int bufsize = bitrate * 2;
 1563
 01564            if (string.Equals(videoCodec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1565            {
 01566                return FormattableString.Invariant($" -b:v {bitrate} -bufsize {bufsize}");
 1567            }
 1568
 01569            if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)
 01570                || string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
 1571            {
 01572                return FormattableString.Invariant($" -maxrate {bitrate} -bufsize {bufsize}");
 1573            }
 1574
 01575            if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01576                || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01577                || string.Equals(videoCodec, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1578            {
 1579                // Override the too high default qmin 18 in transcoding preset
 01580                return FormattableString.Invariant($" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsiz
 1581            }
 1582
 01583            if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01584                || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01585                || string.Equals(videoCodec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1586            {
 1587                // VBR in i965 driver may result in pixelated output.
 01588                if (_mediaEncoder.IsVaapiDeviceInteli965)
 1589                {
 01590                    return FormattableString.Invariant($" -rc_mode CBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsi
 1591                }
 1592
 01593                return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"
 1594            }
 1595
 01596            if (string.Equals(videoCodec, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 01597                || string.Equals(videoCodec, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase))
 1598            {
 1599                // The `maxrate` and `bufsize` options can potentially lead to performance regression
 1600                // and even encoder hangs, especially when the value is very high.
 01601                return FormattableString.Invariant($" -b:v {bitrate} -qmin -1 -qmax -1");
 1602            }
 1603
 01604            return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
 1605        }
 1606
 1607        private string GetEncoderParam(EncoderPreset? preset, EncoderPreset defaultPreset, EncodingOptions encodingOptio
 1608        {
 01609            var param = string.Empty;
 01610            var encoderPreset = preset ?? defaultPreset;
 01611            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
 1612            {
 01613                var presetString = encoderPreset switch
 01614                {
 01615                    EncoderPreset.auto => EncoderPreset.veryfast.ToString().ToLowerInvariant(),
 01616                    _ => encoderPreset.ToString().ToLowerInvariant()
 01617                };
 1618
 01619                param += " -preset " + presetString;
 1620
 01621                int encodeCrf = encodingOptions.H264Crf;
 01622                if (isLibX265)
 1623                {
 01624                    encodeCrf = encodingOptions.H265Crf;
 1625                }
 1626
 01627                if (encodeCrf >= 0 && encodeCrf <= 51)
 1628                {
 01629                    param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture);
 1630                }
 1631                else
 1632                {
 01633                    string defaultCrf = "23";
 01634                    if (isLibX265)
 1635                    {
 01636                        defaultCrf = "28";
 1637                    }
 1638
 01639                    param += " -crf " + defaultCrf;
 1640                }
 1641            }
 01642            else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1643            {
 1644                // Default to use the recommended preset 10.
 1645                // Omit presets < 5, which are too slow for on the fly encoding.
 1646                // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md
 01647                param += encoderPreset switch
 01648                {
 01649                    EncoderPreset.veryslow => " -preset 5",
 01650                    EncoderPreset.slower => " -preset 6",
 01651                    EncoderPreset.slow => " -preset 7",
 01652                    EncoderPreset.medium => " -preset 8",
 01653                    EncoderPreset.fast => " -preset 9",
 01654                    EncoderPreset.faster => " -preset 10",
 01655                    EncoderPreset.veryfast => " -preset 11",
 01656                    EncoderPreset.superfast => " -preset 12",
 01657                    EncoderPreset.ultrafast => " -preset 13",
 01658                    _ => " -preset 10"
 01659                };
 1660            }
 01661            else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01662                     || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01663                     || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1664            {
 1665                // -compression_level is not reliable on AMD.
 01666                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 1667                {
 01668                    param += encoderPreset switch
 01669                    {
 01670                        EncoderPreset.veryslow => " -compression_level 1",
 01671                        EncoderPreset.slower => " -compression_level 2",
 01672                        EncoderPreset.slow => " -compression_level 3",
 01673                        EncoderPreset.medium => " -compression_level 4",
 01674                        EncoderPreset.fast => " -compression_level 5",
 01675                        EncoderPreset.faster => " -compression_level 6",
 01676                        EncoderPreset.veryfast => " -compression_level 7",
 01677                        EncoderPreset.superfast => " -compression_level 7",
 01678                        EncoderPreset.ultrafast => " -compression_level 7",
 01679                        _ => string.Empty
 01680                    };
 1681                }
 1682            }
 01683            else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv)
 01684                     || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv)
 01685                     || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv)
 1686            {
 01687                EncoderPreset[] valid_presets = [EncoderPreset.veryslow, EncoderPreset.slower, EncoderPreset.slow, Encod
 1688
 01689                param += " -preset " + (valid_presets.Contains(encoderPreset) ? encoderPreset : EncoderPreset.veryfast).
 1690            }
 01691            else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
 01692                        || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_n
 01693                        || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase) // av1 (av1_nven
 01694            )
 1695            {
 01696                param += encoderPreset switch
 01697                {
 01698                        EncoderPreset.veryslow => " -preset p7",
 01699                        EncoderPreset.slower => " -preset p6",
 01700                        EncoderPreset.slow => " -preset p5",
 01701                        EncoderPreset.medium => " -preset p4",
 01702                        EncoderPreset.fast => " -preset p3",
 01703                        EncoderPreset.faster => " -preset p2",
 01704                        _ => " -preset p1"
 01705                };
 1706            }
 01707            else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf)
 01708                        || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf
 01709                        || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase) // av1 (av1_amf)
 01710            )
 1711            {
 01712                param += encoderPreset switch
 01713                {
 01714                        EncoderPreset.veryslow => " -quality quality",
 01715                        EncoderPreset.slower => " -quality quality",
 01716                        EncoderPreset.slow => " -quality quality",
 01717                        EncoderPreset.medium => " -quality balanced",
 01718                        _ => " -quality speed"
 01719                };
 1720
 01721                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01722                    || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1723                {
 01724                    param += " -header_insertion_mode gop";
 1725                }
 1726
 01727                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
 1728                {
 01729                    param += " -gops_per_idr 1";
 1730                }
 1731            }
 01732            else if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase) // h264 (h264_
 01733                        || string.Equals(videoEncoder, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase) // hevc 
 01734            )
 1735            {
 01736                param += encoderPreset switch
 01737                {
 01738                        EncoderPreset.veryslow => " -prio_speed 0",
 01739                        EncoderPreset.slower => " -prio_speed 0",
 01740                        EncoderPreset.slow => " -prio_speed 0",
 01741                        EncoderPreset.medium => " -prio_speed 0",
 01742                        _ => " -prio_speed 1"
 01743                };
 1744            }
 1745
 01746            return param;
 1747        }
 1748
 1749        public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
 1750        {
 01751            if (double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel))
 1752            {
 01753                if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase))
 1754                {
 1755                    // Transcode to level 5.3 (15) and lower for maximum compatibility.
 1756                    // https://en.wikipedia.org/wiki/AV1#Levels
 01757                    if (requestLevel < 0 || requestLevel >= 15)
 1758                    {
 01759                        return "15";
 1760                    }
 1761                }
 01762                else if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 01763                         || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
 1764                {
 1765                    // Transcode to level 5.0 and lower for maximum compatibility.
 1766                    // Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it.
 1767                    // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
 1768                    // MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880.
 01769                    if (requestLevel < 0 || requestLevel >= 150)
 1770                    {
 01771                        return "150";
 1772                    }
 1773                }
 01774                else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
 1775                {
 1776                    // Transcode to level 5.1 and lower for maximum compatibility.
 1777                    // h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
 1778                    // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
 01779                    if (requestLevel < 0 || requestLevel >= 51)
 1780                    {
 01781                        return "51";
 1782                    }
 1783                }
 1784            }
 1785
 01786            return level;
 1787        }
 1788
 1789        /// <summary>
 1790        /// Gets the text subtitle param.
 1791        /// </summary>
 1792        /// <param name="state">The state.</param>
 1793        /// <param name="enableAlpha">Enable alpha processing.</param>
 1794        /// <param name="enableSub2video">Enable sub2video mode.</param>
 1795        /// <returns>System.String.</returns>
 1796        public string GetTextSubtitlesFilter(EncodingJobInfo state, bool enableAlpha, bool enableSub2video)
 1797        {
 01798            var seconds = Math.Round(TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds);
 1799
 1800            // hls always copies timestamps
 01801            var setPtsParam = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive
 01802                ? string.Empty
 01803                : string.Format(CultureInfo.InvariantCulture, ",setpts=PTS -{0}/TB", seconds);
 1804
 01805            var alphaParam = enableAlpha ? ":alpha=1" : string.Empty;
 01806            var sub2videoParam = enableSub2video ? ":sub2video=1" : string.Empty;
 1807
 01808            var fontPath = _pathManager.GetAttachmentFolderPath(state.MediaSource.Id);
 01809            var fontParam = string.Format(
 01810                CultureInfo.InvariantCulture,
 01811                ":fontsdir='{0}'",
 01812                _mediaEncoder.EscapeSubtitleFilterPath(fontPath));
 1813
 01814            if (state.SubtitleStream.IsExternal)
 1815            {
 01816                var charsetParam = string.Empty;
 1817
 01818                if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
 1819                {
 01820                    var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
 01821                            state.SubtitleStream,
 01822                            state.SubtitleStream.Language,
 01823                            state.MediaSource,
 01824                            CancellationToken.None).GetAwaiter().GetResult();
 1825
 01826                    if (!string.IsNullOrEmpty(charenc))
 1827                    {
 01828                        charsetParam = ":charenc=" + charenc;
 1829                    }
 1830                }
 1831
 01832                return string.Format(
 01833                    CultureInfo.InvariantCulture,
 01834                    "subtitles=f='{0}'{1}{2}{3}{4}{5}",
 01835                    _mediaEncoder.EscapeSubtitleFilterPath(state.SubtitleStream.Path),
 01836                    charsetParam,
 01837                    alphaParam,
 01838                    sub2videoParam,
 01839                    fontParam,
 01840                    setPtsParam);
 1841            }
 1842
 01843            var subtitlePath = _subtitleEncoder.GetSubtitleFilePath(
 01844                    state.SubtitleStream,
 01845                    state.MediaSource,
 01846                    CancellationToken.None).GetAwaiter().GetResult();
 1847
 01848            return string.Format(
 01849                CultureInfo.InvariantCulture,
 01850                "subtitles=f='{0}'{1}{2}{3}{4}",
 01851                _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
 01852                alphaParam,
 01853                sub2videoParam,
 01854                fontParam,
 01855                setPtsParam);
 1856        }
 1857
 1858        public double? GetFramerateParam(EncodingJobInfo state)
 1859        {
 01860            var request = state.BaseRequest;
 1861
 01862            if (request.Framerate.HasValue)
 1863            {
 01864                return request.Framerate.Value;
 1865            }
 1866
 01867            var maxrate = request.MaxFramerate;
 1868
 01869            if (maxrate.HasValue && state.VideoStream is not null)
 1870            {
 01871                var contentRate = state.VideoStream.ReferenceFrameRate;
 1872
 01873                if (contentRate.HasValue && contentRate.Value > maxrate.Value)
 1874                {
 01875                    return maxrate;
 1876                }
 1877            }
 1878
 01879            return null;
 1880        }
 1881
 1882        public string GetHlsVideoKeyFrameArguments(
 1883            EncodingJobInfo state,
 1884            string codec,
 1885            int segmentLength,
 1886            bool isEventPlaylist,
 1887            int? startNumber)
 1888        {
 01889            var args = string.Empty;
 01890            var gopArg = string.Empty;
 1891
 01892            var keyFrameArg = string.Format(
 01893                CultureInfo.InvariantCulture,
 01894                " -force_key_frames:0 \"expr:gte(t,n_forced*{0})\"",
 01895                segmentLength);
 1896
 01897            var framerate = state.VideoStream?.RealFrameRate;
 01898            if (framerate.HasValue)
 1899            {
 1900                // This is to make sure keyframe interval is limited to our segment,
 1901                // as forcing keyframes is not enough.
 1902                // Example: we encoded half of desired length, then codec detected
 1903                // scene cut and inserted a keyframe; next forced keyframe would
 1904                // be created outside of segment, which breaks seeking.
 01905                gopArg = string.Format(
 01906                    CultureInfo.InvariantCulture,
 01907                    " -g:v:0 {0} -keyint_min:v:0 {0}",
 01908                    Math.Ceiling(segmentLength * framerate.Value));
 1909            }
 1910
 1911            // Unable to force key frames using these encoders, set key frames by GOP.
 01912            if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01913                || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 01914                || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01915                || string.Equals(codec, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 01916                || string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 01917                || string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 01918                || string.Equals(codec, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase)
 01919                || string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 01920                || string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 01921                || string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase)
 01922                || string.Equals(codec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1923            {
 01924                args += gopArg;
 1925            }
 01926            else if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase)
 01927                     || string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)
 01928                     || string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01929                     || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01930                     || string.Equals(codec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1931            {
 01932                args += keyFrameArg;
 1933
 1934                // prevent the libx264 from post processing to break the set keyframe.
 01935                if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase))
 1936                {
 01937                    args += " -sc_threshold:v:0 0";
 1938                }
 1939            }
 1940            else
 1941            {
 01942                args += keyFrameArg + gopArg;
 1943            }
 1944
 1945            // global_header produced by AMD HEVC VA-API encoder causes non-playable fMP4 on iOS
 01946            if (string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01947                && _mediaEncoder.IsVaapiDeviceAmd)
 1948            {
 01949                args += " -flags:v -global_header";
 1950            }
 1951
 01952            return args;
 1953        }
 1954
 1955        /// <summary>
 1956        /// Gets the video bitrate to specify on the command line.
 1957        /// </summary>
 1958        /// <param name="state">Encoding state.</param>
 1959        /// <param name="videoEncoder">Video encoder to use.</param>
 1960        /// <param name="encodingOptions">Encoding options.</param>
 1961        /// <param name="defaultPreset">Default present to use for encoding.</param>
 1962        /// <returns>Video bitrate.</returns>
 1963        public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, 
 1964        {
 01965            var param = string.Empty;
 1966
 1967            // Tutorials: Enable Intel GuC / HuC firmware loading for Low Power Encoding.
 1968            // https://01.org/group/43/downloads/firmware
 1969            // https://wiki.archlinux.org/title/intel_graphics#Enable_GuC_/_HuC_firmware_loading
 1970            // Intel Low Power Encoding can save unnecessary CPU-GPU synchronization,
 1971            // which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
 01972            var intelLowPowerHwEncoding = false;
 1973
 1974            // Workaround for linux 5.18 to 6.1.3 i915 hang at cost of performance.
 1975            // https://github.com/intel/media-driver/issues/1456
 01976            var enableWaFori915Hang = false;
 1977
 01978            var hardwareAccelerationType = encodingOptions.HardwareAccelerationType;
 1979
 01980            if (hardwareAccelerationType == HardwareAccelerationType.vaapi)
 1981            {
 01982                var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
 1983
 01984                if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
 1985                {
 01986                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder && isIntelVaapiDriver;
 1987                }
 01988                else if (string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
 1989                {
 01990                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder && isIntelVaapiDriver;
 1991                }
 1992            }
 01993            else if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 1994            {
 01995                if (OperatingSystem.IsLinux())
 1996                {
 01997                    var ver = Environment.OSVersion.Version;
 01998                    var isFixedKernel60 = ver.Major == 6 && ver.Minor == 0 && ver >= _minFixedKernel60i915Hang;
 01999                    var isUnaffectedKernel = ver < _minKerneli915Hang || ver > _maxKerneli915Hang;
 2000
 02001                    if (!(isUnaffectedKernel || isFixedKernel60))
 2002                    {
 02003                        var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
 02004                        var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
 02005                                             || vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 02006                        var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
 02007                            && IsVaapiSupported(state)
 02008                            && IsOpenclFullSupported()
 02009                            && !IsIntelVppTonemapAvailable(state, encodingOptions)
 02010                            && IsHwTonemapAvailable(state, encodingOptions);
 2011
 02012                        enableWaFori915Hang = isIntelDecoder && doOclTonemap;
 2013                    }
 2014                }
 2015
 02016                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 2017                {
 02018                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder;
 2019                }
 02020                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2021                {
 02022                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder;
 2023                }
 2024                else
 2025                {
 02026                    enableWaFori915Hang = false;
 2027                }
 2028            }
 2029
 02030            if (intelLowPowerHwEncoding)
 2031            {
 02032                param += " -low_power 1";
 2033            }
 2034
 02035            if (enableWaFori915Hang)
 2036            {
 02037                param += " -async_depth 1";
 2038            }
 2039
 02040            var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
 02041            var encodingPreset = encodingOptions.EncoderPreset;
 2042
 02043            param += GetEncoderParam(encodingPreset, defaultPreset, encodingOptions, videoEncoder, isLibX265);
 02044            param += GetVideoBitrateParam(state, videoEncoder);
 2045
 02046            var framerate = GetFramerateParam(state);
 02047            if (framerate.HasValue)
 2048            {
 02049                param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(CultureInfo.Inv
 2050            }
 2051
 02052            var targetVideoCodec = state.ActualOutputVideoCodec;
 02053            if (string.Equals(targetVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
 02054                || string.Equals(targetVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
 2055            {
 02056                targetVideoCodec = "hevc";
 2057            }
 2058
 02059            var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
 02060            profile = WhiteSpaceRegex().Replace(profile, string.Empty).ToLowerInvariant();
 2061
 02062            var videoProfiles = Array.Empty<string>();
 02063            if (string.Equals("h264", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2064            {
 02065                videoProfiles = _videoProfilesH264;
 2066            }
 02067            else if (string.Equals("hevc", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2068            {
 02069                videoProfiles = _videoProfilesH265;
 2070            }
 02071            else if (string.Equals("av1", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2072            {
 02073                videoProfiles = _videoProfilesAv1;
 2074            }
 2075
 02076            if (!videoProfiles.Contains(profile, StringComparison.OrdinalIgnoreCase))
 2077            {
 02078                profile = string.Empty;
 2079            }
 2080
 2081            // We only transcode to HEVC 8-bit for now, force Main Profile.
 02082            if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)
 02083                || profile.Contains("mainstill", StringComparison.OrdinalIgnoreCase))
 2084            {
 02085                profile = "main";
 2086            }
 2087
 2088            // Extended Profile is not supported by any known h264 encoders, force Main Profile.
 02089            if (profile.Contains("extended", StringComparison.OrdinalIgnoreCase))
 2090            {
 02091                profile = "main";
 2092            }
 2093
 2094            // Only libx264 support encoding H264 High 10 Profile, otherwise force High Profile.
 02095            if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02096                && profile.Contains("high10", StringComparison.OrdinalIgnoreCase))
 2097            {
 02098                profile = "high";
 2099            }
 2100
 2101            // We only need Main profile of AV1 encoders.
 02102            if (videoEncoder.Contains("av1", StringComparison.OrdinalIgnoreCase)
 02103                && (profile.Contains("high", StringComparison.OrdinalIgnoreCase)
 02104                    || profile.Contains("professional", StringComparison.OrdinalIgnoreCase)))
 2105            {
 02106                profile = "main";
 2107            }
 2108
 2109            // h264_vaapi does not support Baseline profile, force Constrained Baseline in this case,
 2110            // which is compatible (and ugly).
 02111            if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02112                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2113            {
 02114                profile = "constrained_baseline";
 2115            }
 2116
 2117            // libx264, h264_{qsv,nvenc,rkmpp} does not support Constrained Baseline profile, force Baseline in this cas
 02118            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02119                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02120                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02121                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02122                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2123            {
 02124                profile = "baseline";
 2125            }
 2126
 2127            // libx264, h264_{qsv,nvenc,vaapi,rkmpp} does not support Constrained High profile, force High in this case.
 02128            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02129                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02130                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02131                 || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02132                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02133                && profile.Contains("high", StringComparison.OrdinalIgnoreCase))
 2134            {
 02135                profile = "high";
 2136            }
 2137
 02138            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02139                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2140            {
 02141                profile = "constrained_baseline";
 2142            }
 2143
 02144            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02145                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2146            {
 02147                profile = "constrained_high";
 2148            }
 2149
 02150            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02151                && profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase))
 2152            {
 02153                profile = "constrained_baseline";
 2154            }
 2155
 02156            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02157                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2158            {
 02159                profile = "constrained_high";
 2160            }
 2161
 02162            if (!string.IsNullOrEmpty(profile))
 2163            {
 2164                // Currently there's no profile option in av1_nvenc encoder
 02165                if (!(string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 02166                      || string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)))
 2167                {
 02168                    param += " -profile:v:0 " + profile;
 2169                }
 2170            }
 2171
 02172            var level = state.GetRequestedLevel(targetVideoCodec);
 2173
 02174            if (!string.IsNullOrEmpty(level))
 2175            {
 02176                level = NormalizeTranscodingLevel(state, level);
 2177
 2178                // libx264, QSV, AMF can adjust the given level to match the output.
 02179                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02180                    || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2181                {
 02182                    param += " -level " + level;
 2183                }
 02184                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2185                {
 2186                    // hevc_qsv use -level 51 instead of -level 153.
 02187                    if (double.TryParse(level, CultureInfo.InvariantCulture, out double hevcLevel))
 2188                    {
 02189                        param += " -level " + (hevcLevel / 3);
 2190                    }
 2191                }
 02192                else if (string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 02193                         || string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 2194                {
 2195                    // libsvtav1 and av1_qsv use -level 60 instead of -level 16
 2196                    // https://aomedia.org/av1/specification/annex-a/
 02197                    if (int.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out int av1Level))
 2198                    {
 02199                        var x = 2 + (av1Level >> 2);
 02200                        var y = av1Level & 3;
 02201                        var res = (x * 10) + y;
 02202                        param += " -level " + res;
 2203                    }
 2204                }
 02205                else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02206                         || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 02207                         || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 2208                {
 02209                    param += " -level " + level;
 2210                }
 02211                else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02212                         || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 02213                         || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase))
 2214                {
 2215                    // level option may cause NVENC to fail.
 2216                    // NVENC cannot adjust the given level, just throw an error.
 2217                }
 02218                else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02219                         || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02220                         || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 2221                {
 2222                    // level option may cause corrupted frames on AMD VAAPI.
 02223                    if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 2224                    {
 02225                        param += " -level " + level;
 2226                    }
 2227                }
 02228                else if (string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 02229                         || string.Equals(videoEncoder, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase))
 2230                {
 02231                    param += " -level " + level;
 2232                }
 02233                else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2234                {
 02235                    param += " -level " + level;
 2236                }
 2237            }
 2238
 02239            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2240            {
 02241                param += " -x264opts:0 subme=0:me_range=16:rc_lookahead=10:me=hex:open_gop=0";
 2242            }
 2243
 02244            if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2245            {
 2246                // libx265 only accept level option in -x265-params.
 2247                // level option may cause libx265 to fail.
 2248                // libx265 cannot adjust the given level, just throw an error.
 02249                param += " -x265-params:0 no-scenecut=1:no-open-gop=1:no-info=1";
 2250
 02251                if (encodingOptions.EncoderPreset < EncoderPreset.ultrafast)
 2252                {
 2253                    // The following params are slower than the ultrafast preset, don't use when ultrafast is selected.
 02254                    param += ":subme=3:merange=25:rc-lookahead=10:me=star:ctu=32:max-tu-size=32:min-cu-size=16:rskip=2:r
 2255                }
 2256            }
 2257
 02258            if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)
 02259                && _mediaEncoder.EncoderVersion >= _minFFmpegSvtAv1Params)
 2260            {
 02261                param += " -svtav1-params:0 rc=1:tune=0:film-grain=0:enable-overlays=1:enable-tf=0";
 2262            }
 2263
 2264            /* Access unit too large: 8192 < 20880 error */
 02265            if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) ||
 02266                 string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) &&
 02267                 _mediaEncoder.EncoderVersion >= _minFFmpegVaapiH26xEncA53CcSei)
 2268            {
 02269                param += " -sei -a53_cc";
 2270            }
 2271
 02272            return param;
 2273        }
 2274
 2275        public bool CanStreamCopyVideo(EncodingJobInfo state, MediaStream videoStream)
 2276        {
 02277            var request = state.BaseRequest;
 2278
 02279            if (!request.AllowVideoStreamCopy)
 2280            {
 02281                return false;
 2282            }
 2283
 02284            if (videoStream.IsInterlaced
 02285                && state.DeInterlace(videoStream.Codec, false))
 2286            {
 02287                return false;
 2288            }
 2289
 02290            if (videoStream.IsAnamorphic ?? false)
 2291            {
 02292                if (request.RequireNonAnamorphic)
 2293                {
 02294                    return false;
 2295                }
 2296            }
 2297
 2298            // Can't stream copy if we're burning in subtitles
 02299            if (request.SubtitleStreamIndex.HasValue
 02300                && request.SubtitleStreamIndex.Value >= 0
 02301                && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
 2302            {
 02303                return false;
 2304            }
 2305
 02306            if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 02307                && videoStream.IsAVC.HasValue
 02308                && !videoStream.IsAVC.Value
 02309                && request.RequireAvc)
 2310            {
 02311                return false;
 2312            }
 2313
 2314            // Source and target codecs must match
 02315            if (string.IsNullOrEmpty(videoStream.Codec)
 02316                || (state.SupportedVideoCodecs.Length != 0
 02317                    && !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparison.OrdinalIgnoreCase)))
 2318            {
 02319                return false;
 2320            }
 2321
 02322            var requestedProfiles = state.GetRequestedProfiles(videoStream.Codec);
 2323
 2324            // If client is requesting a specific video profile, it must match the source
 02325            if (requestedProfiles.Length > 0)
 2326            {
 02327                if (string.IsNullOrEmpty(videoStream.Profile))
 2328                {
 2329                    // return false;
 2330                }
 2331
 02332                var requestedProfile = requestedProfiles[0];
 2333                // strip spaces because they may be stripped out on the query string as well
 02334                if (!string.IsNullOrEmpty(videoStream.Profile)
 02335                    && !requestedProfiles.Contains(videoStream.Profile.Replace(" ", string.Empty, StringComparison.Ordin
 2336                {
 02337                    var currentScore = GetVideoProfileScore(videoStream.Codec, videoStream.Profile);
 02338                    var requestedScore = GetVideoProfileScore(videoStream.Codec, requestedProfile);
 2339
 02340                    if (currentScore == -1 || currentScore > requestedScore)
 2341                    {
 02342                        return false;
 2343                    }
 2344                }
 2345            }
 2346
 02347            var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
 02348            if (requestedRangeTypes.Length > 0)
 2349            {
 02350                if (videoStream.VideoRangeType == VideoRangeType.Unknown)
 2351                {
 02352                    return false;
 2353                }
 2354
 2355                // DOVIWithHDR10 should be compatible with HDR10 supporting players. Same goes with HLG and of course SD
 02356                var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ord
 02357                var requestHasHLG = requestedRangeTypes.Contains(VideoRangeType.HLG.ToString(), StringComparison.Ordinal
 02358                var requestHasSDR = requestedRangeTypes.Contains(VideoRangeType.SDR.ToString(), StringComparison.Ordinal
 2359
 02360                if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreC
 02361                     && !((requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10)
 02362                            || (requestHasHLG && videoStream.VideoRangeType == VideoRangeType.DOVIWithHLG)
 02363                            || (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)
 02364                            || (requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.HDR10Plus)))
 2365                {
 2366                    // Check complicated cases where we need to remove dynamic metadata
 2367                    // Conservatively refuse to copy if the encoder can't remove dynamic metadata,
 2368                    // but a removal is required for compatability reasons.
 02369                    var dynamicHdrMetadataRemovalPlan = ShouldRemoveDynamicHdrMetadata(state);
 02370                    if (!CanEncoderRemoveDynamicHdrMetadata(dynamicHdrMetadataRemovalPlan, videoStream))
 2371                    {
 02372                        return false;
 2373                    }
 2374                }
 2375            }
 2376
 2377            // Video width must fall within requested value
 02378            if (request.MaxWidth.HasValue
 02379                && (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
 2380            {
 02381                return false;
 2382            }
 2383
 2384            // Video height must fall within requested value
 02385            if (request.MaxHeight.HasValue
 02386                && (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value))
 2387            {
 02388                return false;
 2389            }
 2390
 2391            // Video framerate must fall within requested value
 02392            var requestedFramerate = request.MaxFramerate ?? request.Framerate;
 02393            if (requestedFramerate.HasValue)
 2394            {
 02395                var videoFrameRate = videoStream.ReferenceFrameRate;
 2396
 2397                // Add a little tolerance to the framerate check because some videos might record a framerate
 2398                // that is slightly greater than the intended framerate, but the device can still play it correctly.
 2399                // 0.05 fps tolerance should be safe enough.
 02400                if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value + 0.05f)
 2401                {
 02402                    return false;
 2403                }
 2404            }
 2405
 2406            // Video bitrate must fall within requested value
 02407            if (request.VideoBitRate.HasValue
 02408                && (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value))
 2409            {
 2410                // For LiveTV that has no bitrate, let's try copy if other conditions are met
 02411                if (string.IsNullOrWhiteSpace(request.LiveStreamId) || videoStream.BitRate.HasValue)
 2412                {
 02413                    return false;
 2414                }
 2415            }
 2416
 02417            var maxBitDepth = state.GetRequestedVideoBitDepth(videoStream.Codec);
 02418            if (maxBitDepth.HasValue)
 2419            {
 02420                if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > maxBitDepth.Value)
 2421                {
 02422                    return false;
 2423                }
 2424            }
 2425
 02426            var maxRefFrames = state.GetRequestedMaxRefFrames(videoStream.Codec);
 02427            if (maxRefFrames.HasValue
 02428                && videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > maxRefFrames.Value)
 2429            {
 02430                return false;
 2431            }
 2432
 2433            // If a specific level was requested, the source must match or be less than
 02434            var level = state.GetRequestedLevel(videoStream.Codec);
 02435            if (double.TryParse(level, CultureInfo.InvariantCulture, out var requestLevel))
 2436            {
 02437                if (!videoStream.Level.HasValue)
 2438                {
 2439                    // return false;
 2440                }
 2441
 02442                if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
 2443                {
 02444                    return false;
 2445                }
 2446            }
 2447
 02448            if (string.Equals(state.InputContainer, "avi", StringComparison.OrdinalIgnoreCase)
 02449                && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)
 02450                && !(videoStream.IsAVC ?? false))
 2451            {
 2452                // see Coach S01E01 - Kelly and the Professor(0).avi
 02453                return false;
 2454            }
 2455
 02456            return true;
 2457        }
 2458
 2459        public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, IEnumerable<string> supportedAudi
 2460        {
 02461            var request = state.BaseRequest;
 2462
 02463            if (!request.AllowAudioStreamCopy)
 2464            {
 02465                return false;
 2466            }
 2467
 02468            var maxBitDepth = state.GetRequestedAudioBitDepth(audioStream.Codec);
 02469            if (maxBitDepth.HasValue
 02470                && audioStream.BitDepth.HasValue
 02471                && audioStream.BitDepth.Value > maxBitDepth.Value)
 2472            {
 02473                return false;
 2474            }
 2475
 2476            // Source and target codecs must match
 02477            if (string.IsNullOrEmpty(audioStream.Codec)
 02478                || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparison.OrdinalIgnoreCase))
 2479            {
 02480                return false;
 2481            }
 2482
 2483            // Channels must fall within requested value
 02484            var channels = state.GetRequestedAudioChannels(audioStream.Codec);
 02485            if (channels.HasValue)
 2486            {
 02487                if (!audioStream.Channels.HasValue || audioStream.Channels.Value <= 0)
 2488                {
 02489                    return false;
 2490                }
 2491
 02492                if (audioStream.Channels.Value > channels.Value)
 2493                {
 02494                    return false;
 2495                }
 2496            }
 2497
 2498            // Sample rate must fall within requested value
 02499            if (request.AudioSampleRate.HasValue)
 2500            {
 02501                if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value <= 0)
 2502                {
 02503                    return false;
 2504                }
 2505
 02506                if (audioStream.SampleRate.Value > request.AudioSampleRate.Value)
 2507                {
 02508                    return false;
 2509                }
 2510            }
 2511
 2512            // Audio bitrate must fall within requested value
 02513            if (request.AudioBitRate.HasValue
 02514                && audioStream.BitRate.HasValue
 02515                && audioStream.BitRate.Value > request.AudioBitRate.Value)
 2516            {
 02517                return false;
 2518            }
 2519
 02520            return request.EnableAutoStreamCopy;
 2521        }
 2522
 2523        public int GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideo
 2524        {
 02525            var bitrate = request.VideoBitRate;
 2526
 02527            if (videoStream is not null)
 2528            {
 02529                var isUpscaling = request.Height.HasValue
 02530                    && videoStream.Height.HasValue
 02531                    && request.Height.Value > videoStream.Height.Value
 02532                    && request.Width.HasValue
 02533                    && videoStream.Width.HasValue
 02534                    && request.Width.Value > videoStream.Width.Value;
 2535
 2536                // Don't allow bitrate increases unless upscaling
 02537                if (!isUpscaling && bitrate.HasValue && videoStream.BitRate.HasValue)
 2538                {
 02539                    bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value);
 2540                }
 2541
 02542                if (bitrate.HasValue)
 2543                {
 02544                    var inputVideoCodec = videoStream.Codec;
 02545                    bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
 2546
 2547                    // If a max bitrate was requested, don't let the scaled bitrate exceed it
 02548                    if (request.VideoBitRate.HasValue)
 2549                    {
 02550                        bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
 2551                    }
 2552                }
 2553            }
 2554
 2555            // Cap the max target bitrate to intMax/2 to satisfy the bufsize=bitrate*2.
 02556            return Math.Min(bitrate ?? 0, int.MaxValue / 2);
 2557        }
 2558
 2559        private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
 2560        {
 2561            // these values were chosen from testing to improve low bitrate streams
 02562            if (sourceBitrate <= 2000000)
 2563            {
 02564                sourceBitrate = Convert.ToInt32(sourceBitrate * 2.5);
 2565            }
 02566            else if (sourceBitrate <= 3000000)
 2567            {
 02568                sourceBitrate *= 2;
 2569            }
 2570
 02571            var bitrate = Math.Min(sourceBitrate, requestedBitrate);
 2572
 02573            return bitrate;
 2574        }
 2575
 2576        private static double GetVideoBitrateScaleFactor(string codec)
 2577        {
 2578            // hevc & vp9 - 40% more efficient than h.264
 02579            if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 02580                || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
 02581                || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
 2582            {
 02583                return .6;
 2584            }
 2585
 2586            // av1 - 50% more efficient than h.264
 02587            if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 2588            {
 02589                return .5;
 2590            }
 2591
 02592            return 1;
 2593        }
 2594
 2595        public static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
 2596        {
 02597            var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
 02598            var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
 2599
 2600            // Don't scale the real bitrate lower than the requested bitrate
 02601            var scaleFactor = Math.Max(outputScaleFactor / inputScaleFactor, 1);
 2602
 02603            if (bitrate <= 500000)
 2604            {
 02605                scaleFactor = Math.Max(scaleFactor, 4);
 2606            }
 02607            else if (bitrate <= 1000000)
 2608            {
 02609                scaleFactor = Math.Max(scaleFactor, 3);
 2610            }
 02611            else if (bitrate <= 2000000)
 2612            {
 02613                scaleFactor = Math.Max(scaleFactor, 2.5);
 2614            }
 02615            else if (bitrate <= 3000000)
 2616            {
 02617                scaleFactor = Math.Max(scaleFactor, 2);
 2618            }
 02619            else if (bitrate >= 30000000)
 2620            {
 2621                // Don't scale beyond 30Mbps, it is hardly visually noticeable for most codecs with our prefer speed enc
 2622                // and will cause extremely high bitrate to be used for av1->h264 transcoding that will overload clients
 02623                scaleFactor = 1;
 2624            }
 2625
 02626            return Convert.ToInt32(scaleFactor * bitrate);
 2627        }
 2628
 2629        public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChanne
 2630        {
 02631            return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels);
 2632        }
 2633
 2634        public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudio
 2635        {
 02636            if (audioStream is null)
 2637            {
 02638                return null;
 2639            }
 2640
 02641            var inputChannels = audioStream.Channels ?? 0;
 02642            var outputChannels = outputAudioChannels ?? 0;
 02643            var bitrate = audioBitRate ?? int.MaxValue;
 2644
 02645            if (string.IsNullOrEmpty(audioCodec)
 02646                || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
 02647                || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
 02648                || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
 02649                || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
 02650                || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
 02651                || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
 2652            {
 02653                return (inputChannels, outputChannels) switch
 02654                {
 02655                    (>= 6, >= 6 or 0) => Math.Min(640000, bitrate),
 02656                    (> 0, > 0) => Math.Min(outputChannels * 128000, bitrate),
 02657                    (> 0, _) => Math.Min(inputChannels * 128000, bitrate),
 02658                    (_, _) => Math.Min(384000, bitrate)
 02659                };
 2660            }
 2661
 02662            if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
 02663                || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
 2664            {
 02665                return (inputChannels, outputChannels) switch
 02666                {
 02667                    (>= 6, >= 6 or 0) => Math.Min(768000, bitrate),
 02668                    (> 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
 02669                    (> 0, _) => Math.Min(inputChannels * 136000, bitrate),
 02670                    (_, _) => Math.Min(672000, bitrate)
 02671                };
 2672            }
 2673
 2674            // Empty bitrate area is not allow on iOS
 2675            // Default audio bitrate to 128K per channel if we don't have codec specific defaults
 2676            // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
 02677            return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
 2678        }
 2679
 2680        public string GetAudioVbrModeParam(string encoder, int bitrate, int channels)
 2681        {
 02682            var bitratePerChannel = bitrate / Math.Max(channels, 1);
 02683            if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
 2684            {
 02685                return " -vbr:a " + bitratePerChannel switch
 02686                {
 02687                    < 32000 => "1",
 02688                    < 48000 => "2",
 02689                    < 64000 => "3",
 02690                    < 96000 => "4",
 02691                    _ => "5"
 02692                };
 2693            }
 2694
 02695            if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase))
 2696            {
 2697                // lame's VBR is only good for a certain bitrate range
 2698                // For very low and very high bitrate, use abr mode
 02699                if (bitratePerChannel is < 122500 and > 48000)
 2700                {
 02701                    return " -qscale:a " + bitratePerChannel switch
 02702                    {
 02703                        < 64000 => "6",
 02704                        < 88000 => "4",
 02705                        < 112000 => "2",
 02706                        _ => "0"
 02707                    };
 2708                }
 2709
 02710                return " -abr:a 1" + " -b:a " + bitrate;
 2711            }
 2712
 02713            if (string.Equals(encoder, "aac_at", StringComparison.OrdinalIgnoreCase))
 2714            {
 2715                // aac_at's CVBR mode
 02716                return " -aac_at_mode:a 2" + " -b:a " + bitrate;
 2717            }
 2718
 02719            if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase))
 2720            {
 02721                return " -qscale:a " + bitratePerChannel switch
 02722                {
 02723                    < 40000 => "0",
 02724                    < 56000 => "2",
 02725                    < 80000 => "4",
 02726                    < 112000 => "6",
 02727                    _ => "8"
 02728                };
 2729            }
 2730
 02731            return null;
 2732        }
 2733
 2734        public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
 2735        {
 02736            var channels = state.OutputAudioChannels;
 2737
 02738            var filters = new List<string>();
 2739
 02740            if (channels is 2 && state.AudioStream?.Channels is > 2)
 2741            {
 02742                var hasDownMixFilter = DownMixAlgorithmsHelper.AlgorithmFilterStrings.TryGetValue((encodingOptions.DownM
 02743                if (hasDownMixFilter)
 2744                {
 02745                    filters.Add(downMixFilterString);
 2746                }
 2747
 02748                if (!encodingOptions.DownMixAudioBoost.Equals(1))
 2749                {
 02750                    filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
 2751                }
 2752            }
 2753
 02754            var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
 02755            if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && ShouldEncodeSubtitle(st
 2756            {
 02757                var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds;
 2758
 02759                filters.Add(
 02760                    string.Format(
 02761                        CultureInfo.InvariantCulture,
 02762                        "asetpts=PTS-{0}/TB",
 02763                        Math.Round(seconds)));
 2764            }
 2765
 02766            if (filters.Count > 0)
 2767            {
 02768                return " -af \"" + string.Join(',', filters) + "\"";
 2769            }
 2770
 02771            return string.Empty;
 2772        }
 2773
 2774        /// <summary>
 2775        /// Gets the number of audio channels to specify on the command line.
 2776        /// </summary>
 2777        /// <param name="state">The state.</param>
 2778        /// <param name="audioStream">The audio stream.</param>
 2779        /// <param name="outputAudioCodec">The output audio codec.</param>
 2780        /// <returns>System.Nullable{System.Int32}.</returns>
 2781        public int? GetNumAudioChannelsParam(EncodingJobInfo state, MediaStream audioStream, string outputAudioCodec)
 2782        {
 02783            if (audioStream is null)
 2784            {
 02785                return null;
 2786            }
 2787
 02788            var request = state.BaseRequest;
 2789
 02790            var codec = outputAudioCodec ?? string.Empty;
 2791
 02792            int? resultChannels = state.GetRequestedAudioChannels(codec);
 2793
 02794            var inputChannels = audioStream.Channels;
 2795
 02796            if (inputChannels > 0)
 2797            {
 02798                resultChannels = inputChannels < resultChannels ? inputChannels : resultChannels ?? inputChannels;
 2799            }
 2800
 02801            var isTranscodingAudio = !IsCopyCodec(codec);
 2802
 02803            if (isTranscodingAudio)
 2804            {
 02805                var audioEncoder = GetAudioEncoder(state);
 02806                if (!_audioTranscodeChannelLookup.TryGetValue(audioEncoder, out var transcoderChannelLimit))
 2807                {
 2808                    // Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many c
 02809                    transcoderChannelLimit = 8;
 2810                }
 2811
 2812                // Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelL
 02813                resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? tr
 2814
 02815                if (request.TranscodingMaxAudioChannels < resultChannels)
 2816                {
 02817                    resultChannels = request.TranscodingMaxAudioChannels;
 2818                }
 2819
 2820                // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
 2821                // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_d
 02822                if (state.TranscodingType != TranscodingJobType.Progressive
 02823                    && ((resultChannels > 2 && resultChannels < 6) || resultChannels == 7))
 2824                {
 2825                    // We can let FFMpeg supply an extra LFE channel for 5ch and 7ch to make them 5.1 and 7.1
 02826                    if (resultChannels == 5)
 2827                    {
 02828                        resultChannels = 6;
 2829                    }
 02830                    else if (resultChannels == 7)
 2831                    {
 02832                        resultChannels = 8;
 2833                    }
 2834                    else
 2835                    {
 2836                        // For other weird layout, just downmix to stereo for compatibility
 02837                        resultChannels = 2;
 2838                    }
 2839                }
 2840            }
 2841
 02842            return resultChannels;
 2843        }
 2844
 2845        /// <summary>
 2846        /// Enforces the resolution limit.
 2847        /// </summary>
 2848        /// <param name="state">The state.</param>
 2849        public void EnforceResolutionLimit(EncodingJobInfo state)
 2850        {
 02851            var videoRequest = state.BaseRequest;
 2852
 2853            // Switch the incoming params to be ceilings rather than fixed values
 02854            videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
 02855            videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
 2856
 02857            videoRequest.Width = null;
 02858            videoRequest.Height = null;
 02859        }
 2860
 2861        /// <summary>
 2862        /// Gets the fast seek command line parameter.
 2863        /// </summary>
 2864        /// <param name="state">The state.</param>
 2865        /// <param name="options">The options.</param>
 2866        /// <param name="segmentContainer">Segment Container.</param>
 2867        /// <returns>System.String.</returns>
 2868        /// <value>The fast seek command line parameter.</value>
 2869        public string GetFastSeekCommandLineParameter(EncodingJobInfo state, EncodingOptions options, string segmentCont
 2870        {
 02871            var time = state.BaseRequest.StartTimeTicks ?? 0;
 02872            var maxTime = state.RunTimeTicks ?? 0;
 02873            var seekParam = string.Empty;
 2874
 02875            if (time > 0)
 2876            {
 2877                // For direct streaming/remuxing, we seek at the exact position of the keyframe
 2878                // However, ffmpeg will seek to previous keyframe when the exact time is the input
 2879                // Workaround this by adding 0.5s offset to the seeking time to get the exact keyframe on most videos.
 2880                // This will help subtitle syncing.
 02881                var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCod
 02882                var seekTick = isHlsRemuxing ? time + 5000000L : time;
 2883
 2884                // Seeking beyond EOF makes no sense in transcoding. Clamp the seekTick value to
 2885                // [0, RuntimeTicks - 5.0s], so that the muxer gets packets and avoid error codes.
 02886                if (maxTime > 0)
 2887                {
 02888                    seekTick = Math.Clamp(seekTick, 0, Math.Max(maxTime - 50000000L, 0));
 2889                }
 2890
 02891                seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(seekT
 2892
 02893                if (state.IsVideoRequest)
 2894                {
 02895                    var outputVideoCodec = GetVideoEncoder(state, options);
 02896                    var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
 2897
 2898                    // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking
 2899                    // Disable -noaccurate_seek on mpegts container due to the timestamps issue on some clients,
 2900                    // but it's still required for fMP4 container otherwise the audio can't be synced to the video.
 02901                    if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase)
 02902                        && !string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase)
 02903                        && state.TranscodingType != TranscodingJobType.Progressive
 02904                        && !state.EnableBreakOnNonKeyFrames(outputVideoCodec)
 02905                        && (state.BaseRequest.StartTimeTicks ?? 0) > 0)
 2906                    {
 02907                        seekParam += " -noaccurate_seek";
 2908                    }
 2909                }
 2910            }
 2911
 02912            return seekParam;
 2913        }
 2914
 2915        /// <summary>
 2916        /// Gets the map args.
 2917        /// </summary>
 2918        /// <param name="state">The state.</param>
 2919        /// <returns>System.String.</returns>
 2920        public string GetMapArgs(EncodingJobInfo state)
 2921        {
 2922            // If we don't have known media info
 2923            // If input is video, use -sn to drop subtitles
 2924            // Otherwise just return empty
 02925            if (state.VideoStream is null && state.AudioStream is null)
 2926            {
 02927                return state.IsInputVideo ? "-sn" : string.Empty;
 2928            }
 2929
 2930            // We have media info, but we don't know the stream index
 02931            if (state.VideoStream is not null && state.VideoStream.Index == -1)
 2932            {
 02933                return "-sn";
 2934            }
 2935
 2936            // We have media info, but we don't know the stream index
 02937            if (state.AudioStream is not null && state.AudioStream.Index == -1)
 2938            {
 02939                return state.IsInputVideo ? "-sn" : string.Empty;
 2940            }
 2941
 02942            var args = string.Empty;
 2943
 02944            if (state.VideoStream is not null)
 2945            {
 02946                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 2947
 02948                args += string.Format(
 02949                    CultureInfo.InvariantCulture,
 02950                    "-map 0:{0}",
 02951                    videoStreamIndex);
 2952            }
 2953            else
 2954            {
 2955                // No known video stream
 02956                args += "-vn";
 2957            }
 2958
 02959            if (state.AudioStream is not null)
 2960            {
 02961                int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
 02962                if (state.AudioStream.IsExternal)
 2963                {
 02964                    bool hasExternalGraphicsSubs = state.SubtitleStream is not null
 02965                        && ShouldEncodeSubtitle(state)
 02966                        && state.SubtitleStream.IsExternal
 02967                        && !state.SubtitleStream.IsTextSubtitleStream;
 02968                    int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
 2969
 02970                    args += string.Format(
 02971                        CultureInfo.InvariantCulture,
 02972                        " -map {0}:{1}",
 02973                        externalAudioMapIndex,
 02974                        audioStreamIndex);
 2975                }
 2976                else
 2977                {
 02978                    args += string.Format(
 02979                        CultureInfo.InvariantCulture,
 02980                        " -map 0:{0}",
 02981                        audioStreamIndex);
 2982                }
 2983            }
 2984            else
 2985            {
 02986                args += " -map -0:a";
 2987            }
 2988
 02989            var subtitleMethod = state.SubtitleDeliveryMethod;
 02990            if (state.SubtitleStream is null || subtitleMethod == SubtitleDeliveryMethod.Hls)
 2991            {
 02992                args += " -map -0:s";
 2993            }
 02994            else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
 2995            {
 02996                int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 2997
 02998                args += string.Format(
 02999                    CultureInfo.InvariantCulture,
 03000                    " -map 0:{0}",
 03001                    subtitleStreamIndex);
 3002            }
 03003            else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
 3004            {
 03005                int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3006
 03007                args += string.Format(
 03008                    CultureInfo.InvariantCulture,
 03009                    " -map 1:{0} -sn",
 03010                    externalSubtitleStreamIndex);
 3011            }
 3012
 03013            return args;
 3014        }
 3015
 3016        /// <summary>
 3017        /// Gets the negative map args by filters.
 3018        /// </summary>
 3019        /// <param name="state">The state.</param>
 3020        /// <param name="videoProcessFilters">The videoProcessFilters.</param>
 3021        /// <returns>System.String.</returns>
 3022        public string GetNegativeMapArgsByFilters(EncodingJobInfo state, string videoProcessFilters)
 3023        {
 03024            string args = string.Empty;
 3025
 3026            // http://ffmpeg.org/ffmpeg-all.html#toc-Complex-filtergraphs-1
 03027            if (state.VideoStream is not null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordina
 3028            {
 03029                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 3030
 03031                args += string.Format(
 03032                    CultureInfo.InvariantCulture,
 03033                    "-map -0:{0} ",
 03034                    videoStreamIndex);
 3035            }
 3036
 03037            return args;
 3038        }
 3039
 3040        /// <summary>
 3041        /// Determines which stream will be used for playback.
 3042        /// </summary>
 3043        /// <param name="allStream">All stream.</param>
 3044        /// <param name="desiredIndex">Index of the desired.</param>
 3045        /// <param name="type">The type.</param>
 3046        /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
 3047        /// <returns>MediaStream.</returns>
 3048        public MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, b
 3049        {
 03050            var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
 3051
 03052            if (desiredIndex.HasValue)
 3053            {
 03054                var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
 3055
 03056                if (stream is not null)
 3057                {
 03058                    return stream;
 3059                }
 3060            }
 3061
 03062            if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
 3063            {
 03064                return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
 03065                       streams.FirstOrDefault();
 3066            }
 3067
 3068            // Just return the first one
 03069            return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
 3070        }
 3071
 3072        public static (int? Width, int? Height) GetFixedOutputSize(
 3073            int? videoWidth,
 3074            int? videoHeight,
 3075            int? requestedWidth,
 3076            int? requestedHeight,
 3077            int? requestedMaxWidth,
 3078            int? requestedMaxHeight)
 3079        {
 03080            if (!videoWidth.HasValue && !requestedWidth.HasValue)
 3081            {
 03082                return (null, null);
 3083            }
 3084
 03085            if (!videoHeight.HasValue && !requestedHeight.HasValue)
 3086            {
 03087                return (null, null);
 3088            }
 3089
 03090            int inputWidth = Convert.ToInt32(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture);
 03091            int inputHeight = Convert.ToInt32(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture);
 03092            int outputWidth = requestedWidth ?? inputWidth;
 03093            int outputHeight = requestedHeight ?? inputHeight;
 3094
 3095            // Don't transcode video to bigger than 4k when using HW.
 03096            int maximumWidth = Math.Min(requestedMaxWidth ?? outputWidth, 4096);
 03097            int maximumHeight = Math.Min(requestedMaxHeight ?? outputHeight, 4096);
 3098
 03099            if (outputWidth > maximumWidth || outputHeight > maximumHeight)
 3100            {
 03101                var scaleW = (double)maximumWidth / outputWidth;
 03102                var scaleH = (double)maximumHeight / outputHeight;
 03103                var scale = Math.Min(scaleW, scaleH);
 03104                outputWidth = Math.Min(maximumWidth, Convert.ToInt32(outputWidth * scale));
 03105                outputHeight = Math.Min(maximumHeight, Convert.ToInt32(outputHeight * scale));
 3106            }
 3107
 03108            outputWidth = 2 * (outputWidth / 2);
 03109            outputHeight = 2 * (outputHeight / 2);
 3110
 03111            return (outputWidth, outputHeight);
 3112        }
 3113
 3114        public static bool IsScaleRatioSupported(
 3115            int? videoWidth,
 3116            int? videoHeight,
 3117            int? requestedWidth,
 3118            int? requestedHeight,
 3119            int? requestedMaxWidth,
 3120            int? requestedMaxHeight,
 3121            double? maxScaleRatio)
 3122        {
 03123            var (outWidth, outHeight) = GetFixedOutputSize(
 03124                videoWidth,
 03125                videoHeight,
 03126                requestedWidth,
 03127                requestedHeight,
 03128                requestedMaxWidth,
 03129                requestedMaxHeight);
 3130
 03131            if (!videoWidth.HasValue
 03132                 || !videoHeight.HasValue
 03133                 || !outWidth.HasValue
 03134                 || !outHeight.HasValue
 03135                 || !maxScaleRatio.HasValue
 03136                 || (maxScaleRatio.Value < 1.0f))
 3137            {
 03138                return false;
 3139            }
 3140
 03141            var minScaleRatio = 1.0f / maxScaleRatio;
 03142            var scaleRatioW = (double)outWidth / (double)videoWidth;
 03143            var scaleRatioH = (double)outHeight / (double)videoHeight;
 3144
 03145            if (scaleRatioW < minScaleRatio
 03146                || scaleRatioW > maxScaleRatio
 03147                || scaleRatioH < minScaleRatio
 03148                || scaleRatioH > maxScaleRatio)
 3149            {
 03150                return false;
 3151            }
 3152
 03153            return true;
 3154        }
 3155
 3156        public static string GetHwScaleFilter(
 3157            string hwScalePrefix,
 3158            string hwScaleSuffix,
 3159            string videoFormat,
 3160            bool swapOutputWandH,
 3161            int? videoWidth,
 3162            int? videoHeight,
 3163            int? requestedWidth,
 3164            int? requestedHeight,
 3165            int? requestedMaxWidth,
 3166            int? requestedMaxHeight)
 3167        {
 03168            var (outWidth, outHeight) = GetFixedOutputSize(
 03169                videoWidth,
 03170                videoHeight,
 03171                requestedWidth,
 03172                requestedHeight,
 03173                requestedMaxWidth,
 03174                requestedMaxHeight);
 3175
 03176            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03177            var isSizeFixed = !videoWidth.HasValue
 03178                || outWidth.Value != videoWidth.Value
 03179                || !videoHeight.HasValue
 03180                || outHeight.Value != videoHeight.Value;
 3181
 03182            var swpOutW = swapOutputWandH ? outHeight.Value : outWidth.Value;
 03183            var swpOutH = swapOutputWandH ? outWidth.Value : outHeight.Value;
 3184
 03185            var arg1 = isSizeFixed ? $"=w={swpOutW}:h={swpOutH}" : string.Empty;
 03186            var arg2 = isFormatFixed ? $"format={videoFormat}" : string.Empty;
 03187            if (isFormatFixed)
 3188            {
 03189                arg2 = (isSizeFixed ? ':' : '=') + arg2;
 3190            }
 3191
 03192            if (!string.IsNullOrEmpty(hwScaleSuffix) && (isSizeFixed || isFormatFixed))
 3193            {
 03194                return string.Format(
 03195                    CultureInfo.InvariantCulture,
 03196                    "{0}_{1}{2}{3}",
 03197                    hwScalePrefix ?? "scale",
 03198                    hwScaleSuffix,
 03199                    arg1,
 03200                    arg2);
 3201            }
 3202
 03203            return string.Empty;
 3204        }
 3205
 3206        public static string GetGraphicalSubPreProcessFilters(
 3207            int? videoWidth,
 3208            int? videoHeight,
 3209            int? subtitleWidth,
 3210            int? subtitleHeight,
 3211            int? requestedWidth,
 3212            int? requestedHeight,
 3213            int? requestedMaxWidth,
 3214            int? requestedMaxHeight)
 3215        {
 03216            var (outWidth, outHeight) = GetFixedOutputSize(
 03217                videoWidth,
 03218                videoHeight,
 03219                requestedWidth,
 03220                requestedHeight,
 03221                requestedMaxWidth,
 03222                requestedMaxHeight);
 3223
 03224            if (!outWidth.HasValue
 03225                || !outHeight.HasValue
 03226                || outWidth.Value <= 0
 03227                || outHeight.Value <= 0)
 3228            {
 03229                return string.Empty;
 3230            }
 3231
 3232            // Automatically add padding based on subtitle input
 03233            var filters = @"scale,scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:blac
 3234
 03235            if (subtitleWidth.HasValue
 03236                && subtitleHeight.HasValue
 03237                && subtitleWidth.Value > 0
 03238                && subtitleHeight.Value > 0)
 3239            {
 03240                var videoDar = (double)outWidth.Value / outHeight.Value;
 03241                var subtitleDar = (double)subtitleWidth.Value / subtitleHeight.Value;
 3242
 3243                // No need to add padding when DAR is the same -> 1080p PGSSUB on 2160p video
 03244                if (Math.Abs(videoDar - subtitleDar) < 0.01f)
 3245                {
 03246                    filters = @"scale,scale={0}:{1}:fast_bilinear";
 3247                }
 3248            }
 3249
 03250            return string.Format(
 03251                CultureInfo.InvariantCulture,
 03252                filters,
 03253                outWidth.Value,
 03254                outHeight.Value);
 3255        }
 3256
 3257        public static string GetAlphaSrcFilter(
 3258            EncodingJobInfo state,
 3259            int? videoWidth,
 3260            int? videoHeight,
 3261            int? requestedWidth,
 3262            int? requestedHeight,
 3263            int? requestedMaxWidth,
 3264            int? requestedMaxHeight,
 3265            float? framerate)
 3266        {
 03267            var reqTicks = state.BaseRequest.StartTimeTicks ?? 0;
 03268            var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture
 03269            var (outWidth, outHeight) = GetFixedOutputSize(
 03270                videoWidth,
 03271                videoHeight,
 03272                requestedWidth,
 03273                requestedHeight,
 03274                requestedMaxWidth,
 03275                requestedMaxHeight);
 3276
 03277            if (outWidth.HasValue && outHeight.HasValue)
 3278            {
 03279                return string.Format(
 03280                    CultureInfo.InvariantCulture,
 03281                    "alphasrc=s={0}x{1}:r={2}:start='{3}'",
 03282                    outWidth.Value,
 03283                    outHeight.Value,
 03284                    framerate ?? 25,
 03285                    reqTicks > 0 ? startTime : 0);
 3286            }
 3287
 03288            return string.Empty;
 3289        }
 3290
 3291        public static string GetSwScaleFilter(
 3292            EncodingJobInfo state,
 3293            EncodingOptions options,
 3294            string videoEncoder,
 3295            int? videoWidth,
 3296            int? videoHeight,
 3297            Video3DFormat? threedFormat,
 3298            int? requestedWidth,
 3299            int? requestedHeight,
 3300            int? requestedMaxWidth,
 3301            int? requestedMaxHeight)
 3302        {
 03303            var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 03304            var isMjpeg = videoEncoder is not null && videoEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase)
 03305            var scaleVal = isV4l2 ? 64 : 2;
 03306            var targetAr = isMjpeg ? "(a*sar)" : "a"; // manually calculate AR when using mjpeg encoder
 3307
 3308            // If fixed dimensions were supplied
 03309            if (requestedWidth.HasValue && requestedHeight.HasValue)
 3310            {
 03311                if (isV4l2)
 3312                {
 03313                    var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 03314                    var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3315
 03316                    return string.Format(
 03317                            CultureInfo.InvariantCulture,
 03318                            "scale=trunc({0}/64)*64:trunc({1}/2)*2",
 03319                            widthParam,
 03320                            heightParam);
 3321                }
 3322
 03323                return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
 3324            }
 3325
 3326            // If Max dimensions were supplied, for width selects lowest even number between input width and width req s
 3327
 03328            if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
 3329            {
 03330                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 03331                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3332
 03333                return string.Format(
 03334                    CultureInfo.InvariantCulture,
 03335                    @"scale=trunc(min(max(iw\,ih*{3})\,min({0}\,{1}*{3}))/{2})*{2}:trunc(min(max(iw/{3}\,ih)\,min({0}/{3
 03336                    maxWidthParam,
 03337                    maxHeightParam,
 03338                    scaleVal,
 03339                    targetAr);
 3340            }
 3341
 3342            // If a fixed width was requested
 03343            if (requestedWidth.HasValue)
 3344            {
 03345                if (threedFormat.HasValue)
 3346                {
 3347                    // This method can handle 0 being passed in for the requested height
 03348                    return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0);
 3349                }
 3350
 03351                var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 3352
 03353                return string.Format(
 03354                    CultureInfo.InvariantCulture,
 03355                    "scale={0}:trunc(ow/{1}/2)*2",
 03356                    widthParam,
 03357                    targetAr);
 3358            }
 3359
 3360            // If a fixed height was requested
 03361            if (requestedHeight.HasValue)
 3362            {
 03363                var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3364
 03365                return string.Format(
 03366                    CultureInfo.InvariantCulture,
 03367                    "scale=trunc(oh*{2}/{1})*{1}:{0}",
 03368                    heightParam,
 03369                    scaleVal,
 03370                    targetAr);
 3371            }
 3372
 3373            // If a max width was requested
 03374            if (requestedMaxWidth.HasValue)
 3375            {
 03376                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 3377
 03378                return string.Format(
 03379                    CultureInfo.InvariantCulture,
 03380                    @"scale=trunc(min(max(iw\,ih*{2})\,{0})/{1})*{1}:trunc(ow/{2}/2)*2",
 03381                    maxWidthParam,
 03382                    scaleVal,
 03383                    targetAr);
 3384            }
 3385
 3386            // If a max height was requested
 03387            if (requestedMaxHeight.HasValue)
 3388            {
 03389                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3390
 03391                return string.Format(
 03392                    CultureInfo.InvariantCulture,
 03393                    @"scale=trunc(oh*{2}/{1})*{1}:min(max(iw/{2}\,ih)\,{0})",
 03394                    maxHeightParam,
 03395                    scaleVal,
 03396                    targetAr);
 3397            }
 3398
 03399            return string.Empty;
 3400        }
 3401
 3402        private static string GetFixedSwScaleFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight
 3403        {
 03404            var widthParam = requestedWidth.ToString(CultureInfo.InvariantCulture);
 03405            var heightParam = requestedHeight.ToString(CultureInfo.InvariantCulture);
 3406
 03407            string filter = null;
 3408
 03409            if (threedFormat.HasValue)
 3410            {
 03411                switch (threedFormat.Value)
 3412                {
 3413                    case Video3DFormat.HalfSideBySide:
 03414                        filter = @"crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(i
 3415                        // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars
 03416                        break;
 3417                    case Video3DFormat.FullSideBySide:
 03418                        filter = @"crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3419                        // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the s
 03420                        break;
 3421                    case Video3DFormat.HalfTopAndBottom:
 03422                        filter = @"crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(
 3423                        // htab crop height in half,scale to correct size, set the display aspect,crop out any black bar
 03424                        break;
 3425                    case Video3DFormat.FullTopAndBottom:
 03426                        filter = @"crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3427                        // ftab crop height in half, set the display aspect,crop out any black bars we may have made the
 3428                        break;
 3429                    default:
 3430                        break;
 3431                }
 3432            }
 3433
 3434            // default
 03435            if (filter is null)
 3436            {
 03437                if (requestedHeight > 0)
 3438                {
 03439                    filter = "scale=trunc({0}/2)*2:trunc({1}/2)*2";
 3440                }
 3441                else
 3442                {
 03443                    filter = "scale={0}:trunc({0}/a/2)*2";
 3444                }
 3445            }
 3446
 03447            return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam);
 3448        }
 3449
 3450        public static string GetSwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options)
 3451        {
 03452            var doubleRateDeint = options.DeinterlaceDoubleRate && state.VideoStream?.ReferenceFrameRate <= 30;
 03453            return string.Format(
 03454                CultureInfo.InvariantCulture,
 03455                "{0}={1}:-1:0",
 03456                options.DeinterlaceMethod.ToString().ToLowerInvariant(),
 03457                doubleRateDeint ? "1" : "0");
 3458        }
 3459
 3460        public string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix)
 3461        {
 03462            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03463            if (hwDeintSuffix.Contains("cuda", StringComparison.OrdinalIgnoreCase))
 3464            {
 03465                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3466
 03467                return string.Format(
 03468                    CultureInfo.InvariantCulture,
 03469                    "{0}_cuda={1}:-1:0",
 03470                    useBwdif ? "bwdif" : "yadif",
 03471                    doubleRateDeint ? "1" : "0");
 3472            }
 3473
 03474            if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
 3475            {
 03476                return string.Format(
 03477                    CultureInfo.InvariantCulture,
 03478                    "deinterlace_vaapi=rate={0}",
 03479                    doubleRateDeint ? "field" : "frame");
 3480            }
 3481
 03482            if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
 3483            {
 03484                return "deinterlace_qsv=mode=2";
 3485            }
 3486
 03487            if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
 3488            {
 03489                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3490
 03491                return string.Format(
 03492                    CultureInfo.InvariantCulture,
 03493                    "{0}_videotoolbox={1}:-1:0",
 03494                    useBwdif ? "bwdif" : "yadif",
 03495                    doubleRateDeint ? "1" : "0");
 3496            }
 3497
 03498            return string.Empty;
 3499        }
 3500
 3501        private string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat, bool forc
 3502        {
 03503            if (string.IsNullOrEmpty(hwTonemapSuffix))
 3504            {
 03505                return string.Empty;
 3506            }
 3507
 03508            var args = string.Empty;
 03509            var algorithm = options.TonemappingAlgorithm.ToString().ToLowerInvariant();
 03510            var mode = options.TonemappingMode.ToString().ToLowerInvariant();
 03511            var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 03512            var rangeString = range.ToString().ToLowerInvariant();
 3513
 03514            if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
 3515            {
 03516                var doVaVppProcamp = false;
 03517                var procampParams = string.Empty;
 03518                if (options.VppTonemappingBrightness != 0
 03519                    && options.VppTonemappingBrightness >= -100
 03520                    && options.VppTonemappingBrightness <= 100)
 3521                {
 03522                    procampParams += "procamp_vaapi=b={0}";
 03523                    doVaVppProcamp = true;
 3524                }
 3525
 03526                if (options.VppTonemappingContrast > 1
 03527                    && options.VppTonemappingContrast <= 10)
 3528                {
 03529                    procampParams += doVaVppProcamp ? ":c={1}" : "procamp_vaapi=c={1}";
 03530                    doVaVppProcamp = true;
 3531                }
 3532
 03533                args = procampParams + "{2}tonemap_vaapi=format={3}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
 3534
 03535                return string.Format(
 03536                        CultureInfo.InvariantCulture,
 03537                        args,
 03538                        options.VppTonemappingBrightness,
 03539                        options.VppTonemappingContrast,
 03540                        doVaVppProcamp ? "," : string.Empty,
 03541                        videoFormat ?? "nv12");
 3542            }
 3543            else
 3544            {
 03545                args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
 3546
 03547                var useLegacyTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode
 03548                                           && _legacyTonemapModes.Contains(options.TonemappingMode);
 3549
 03550                var useAdvancedTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegAdvancedTonemapMode
 03551                                              && _advancedTonemapModes.Contains(options.TonemappingMode);
 3552
 03553                if (useLegacyTonemapModes || useAdvancedTonemapModes)
 3554                {
 03555                    args += ":tonemap_mode={5}";
 3556                }
 3557
 03558                if (options.TonemappingParam != 0)
 3559                {
 03560                    args += ":param={6}";
 3561                }
 3562
 03563                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3564                {
 03565                    args += ":range={7}";
 3566                }
 3567            }
 3568
 03569            return string.Format(
 03570                    CultureInfo.InvariantCulture,
 03571                    args,
 03572                    hwTonemapSuffix,
 03573                    videoFormat ?? "nv12",
 03574                    algorithm,
 03575                    options.TonemappingPeak,
 03576                    options.TonemappingDesat,
 03577                    mode,
 03578                    options.TonemappingParam,
 03579                    rangeString);
 3580        }
 3581
 3582        private string GetLibplaceboFilter(
 3583            EncodingOptions options,
 3584            string videoFormat,
 3585            bool doTonemap,
 3586            int? videoWidth,
 3587            int? videoHeight,
 3588            int? requestedWidth,
 3589            int? requestedHeight,
 3590            int? requestedMaxWidth,
 3591            int? requestedMaxHeight,
 3592            bool forceFullRange)
 3593        {
 03594            var (outWidth, outHeight) = GetFixedOutputSize(
 03595                videoWidth,
 03596                videoHeight,
 03597                requestedWidth,
 03598                requestedHeight,
 03599                requestedMaxWidth,
 03600                requestedMaxHeight);
 3601
 03602            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03603            var isSizeFixed = !videoWidth.HasValue
 03604                || outWidth.Value != videoWidth.Value
 03605                || !videoHeight.HasValue
 03606                || outHeight.Value != videoHeight.Value;
 3607
 03608            var sizeArg = isSizeFixed ? (":w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty;
 03609            var formatArg = isFormatFixed ? (":format=" + videoFormat) : string.Empty;
 03610            var tonemapArg = string.Empty;
 3611
 03612            if (doTonemap)
 3613            {
 03614                var algorithm = options.TonemappingAlgorithm;
 03615                var algorithmString = "clip";
 03616                var mode = options.TonemappingMode;
 03617                var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 3618
 03619                if (algorithm == TonemappingAlgorithm.bt2390)
 3620                {
 03621                    algorithmString = "bt.2390";
 3622                }
 03623                else if (algorithm != TonemappingAlgorithm.none)
 3624                {
 03625                    algorithmString = algorithm.ToString().ToLowerInvariant();
 3626                }
 3627
 03628                tonemapArg = $":tonemapping={algorithmString}:peak_detect=0:color_primaries=bt709:color_trc=bt709:colors
 3629
 03630                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3631                {
 03632                    tonemapArg += ":range=" + range.ToString().ToLowerInvariant();
 3633                }
 3634            }
 3635
 03636            return string.Format(
 03637                CultureInfo.InvariantCulture,
 03638                "libplacebo=upscaler=none:downscaler=none{0}{1}{2}",
 03639                sizeArg,
 03640                formatArg,
 03641                tonemapArg);
 3642        }
 3643
 3644        public string GetVideoTransposeDirection(EncodingJobInfo state)
 3645        {
 03646            return (state.VideoStream?.Rotation ?? 0) switch
 03647            {
 03648                90 => "cclock",
 03649                180 => "reversal",
 03650                -90 => "clock",
 03651                -180 => "reversal",
 03652                _ => string.Empty
 03653            };
 3654        }
 3655
 3656        /// <summary>
 3657        /// Gets the parameter of software filter chain.
 3658        /// </summary>
 3659        /// <param name="state">Encoding state.</param>
 3660        /// <param name="options">Encoding options.</param>
 3661        /// <param name="vidEncoder">Video encoder to use.</param>
 3662        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3663        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetSwVidFilterChain(
 3664            EncodingJobInfo state,
 3665            EncodingOptions options,
 3666            string vidEncoder)
 3667        {
 03668            var inW = state.VideoStream?.Width;
 03669            var inH = state.VideoStream?.Height;
 03670            var reqW = state.BaseRequest.Width;
 03671            var reqH = state.BaseRequest.Height;
 03672            var reqMaxW = state.BaseRequest.MaxWidth;
 03673            var reqMaxH = state.BaseRequest.MaxHeight;
 03674            var threeDFormat = state.MediaSource.Video3DFormat;
 3675
 03676            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03677            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03678            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 03679            var isV4l2Encoder = vidEncoder.Contains("h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 3680
 03681            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03682            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03683            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03684            var doToneMap = IsSwTonemapAvailable(state, options);
 03685            var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI;
 3686
 03687            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03688            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03689            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 3690
 03691            var rotation = state.VideoStream?.Rotation ?? 0;
 03692            var swapWAndH = Math.Abs(rotation) == 90;
 03693            var swpInW = swapWAndH ? inH : inW;
 03694            var swpInH = swapWAndH ? inW : inH;
 3695
 3696            /* Make main filters for video stream */
 03697            var mainFilters = new List<string>();
 3698
 03699            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doToneMap));
 3700
 3701            // INPUT sw surface(memory/copy-back from vram)
 3702            // sw deint
 03703            if (doDeintH2645)
 3704            {
 03705                var deintFilter = GetSwDeinterlaceFilter(state, options);
 03706                mainFilters.Add(deintFilter);
 3707            }
 3708
 03709            var outFormat = isSwDecoder ? "yuv420p" : "nv12";
 03710            var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, r
 03711            if (isVaapiEncoder)
 3712            {
 03713                outFormat = "nv12";
 3714            }
 03715            else if (isV4l2Encoder)
 3716            {
 03717                outFormat = "yuv420p";
 3718            }
 3719
 3720            // sw scale
 03721            mainFilters.Add(swScaleFilter);
 3722
 3723            // sw tonemap
 03724            if (doToneMap)
 3725            {
 3726                // tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary
 03727                var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat;
 03728                var tonemapArgString = "tonemapx=tonemap={0}:desat={1}:peak={2}:t=bt709:m=bt709:p=bt709:format={3}";
 3729
 03730                if (options.TonemappingParam != 0)
 3731                {
 03732                    tonemapArgString += ":param={4}";
 3733                }
 3734
 03735                var range = options.TonemappingRange;
 03736                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3737                {
 03738                    tonemapArgString += ":range={5}";
 3739                }
 3740
 03741                var tonemapArgs = string.Format(
 03742                    CultureInfo.InvariantCulture,
 03743                    tonemapArgString,
 03744                    options.TonemappingAlgorithm,
 03745                    options.TonemappingDesat,
 03746                    options.TonemappingPeak,
 03747                    tonemapFormat,
 03748                    options.TonemappingParam,
 03749                    options.TonemappingRange);
 3750
 03751                mainFilters.Add(tonemapArgs);
 3752            }
 3753            else
 3754            {
 3755                // OUTPUT yuv420p/nv12 surface(memory)
 03756                mainFilters.Add("format=" + outFormat);
 3757            }
 3758
 3759            /* Make sub and overlay filters for subtitle stream */
 03760            var subFilters = new List<string>();
 03761            var overlayFilters = new List<string>();
 03762            if (hasTextSubs)
 3763            {
 3764                // subtitles=f='*.ass':alpha=0
 03765                var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03766                mainFilters.Add(textSubtitlesFilter);
 3767            }
 03768            else if (hasGraphicalSubs)
 3769            {
 03770                var subW = state.SubtitleStream?.Width;
 03771                var subH = state.SubtitleStream?.Height;
 03772                var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW
 03773                subFilters.Add(subPreProcFilters);
 03774                overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 3775            }
 3776
 03777            return (mainFilters, subFilters, overlayFilters);
 3778        }
 3779
 3780        /// <summary>
 3781        /// Gets the parameter of Nvidia NVENC filter chain.
 3782        /// </summary>
 3783        /// <param name="state">Encoding state.</param>
 3784        /// <param name="options">Encoding options.</param>
 3785        /// <param name="vidEncoder">Video encoder to use.</param>
 3786        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3787        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFilterChain(
 3788            EncodingJobInfo state,
 3789            EncodingOptions options,
 3790            string vidEncoder)
 3791        {
 03792            if (options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 3793            {
 03794                return (null, null, null);
 3795            }
 3796
 03797            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03798            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03799            var isSwEncoder = !vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 3800
 3801            // legacy cuvid pipeline(copy-back)
 03802            if ((isSwDecoder && isSwEncoder)
 03803                || !IsCudaFullSupported()
 03804                || !_mediaEncoder.SupportsFilter("alphasrc"))
 3805            {
 03806                return GetSwVidFilterChain(state, options, vidEncoder);
 3807            }
 3808
 3809            // preferred nvdec/cuvid + cuda filters + nvenc pipeline
 03810            return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 3811        }
 3812
 3813        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFiltersPrefe
 3814            EncodingJobInfo state,
 3815            EncodingOptions options,
 3816            string vidDecoder,
 3817            string vidEncoder)
 3818        {
 03819            var inW = state.VideoStream?.Width;
 03820            var inH = state.VideoStream?.Height;
 03821            var reqW = state.BaseRequest.Width;
 03822            var reqH = state.BaseRequest.Height;
 03823            var reqMaxW = state.BaseRequest.MaxWidth;
 03824            var reqMaxH = state.BaseRequest.MaxHeight;
 03825            var threeDFormat = state.MediaSource.Video3DFormat;
 3826
 03827            var isNvDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 03828            var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 03829            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03830            var isSwEncoder = !isNvencEncoder;
 03831            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 03832            var isCuInCuOut = isNvDecoder && isNvencEncoder;
 3833
 03834            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03835            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03836            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03837            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03838            var doCuTonemap = IsHwTonemapAvailable(state, options);
 3839
 03840            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03841            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03842            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 03843            var hasAssSubs = hasSubs
 03844                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 03845                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 03846            var subW = state.SubtitleStream?.Width;
 03847            var subH = state.SubtitleStream?.Height;
 3848
 03849            var rotation = state.VideoStream?.Rotation ?? 0;
 03850            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 03851            var doCuTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_cuda");
 03852            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isNvDecoder && doCuTranspose));
 03853            var swpInW = swapWAndH ? inH : inW;
 03854            var swpInH = swapWAndH ? inW : inH;
 3855
 3856            /* Make main filters for video stream */
 03857            var mainFilters = new List<string>();
 3858
 03859            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doCuTonemap));
 3860
 03861            if (isSwDecoder)
 3862            {
 3863                // INPUT sw surface(memory)
 3864                // sw deint
 03865                if (doDeintH2645)
 3866                {
 03867                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 03868                    mainFilters.Add(swDeintFilter);
 3869                }
 3870
 03871                var outFormat = doCuTonemap ? "yuv420p10le" : "yuv420p";
 03872                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 3873                // sw scale
 03874                mainFilters.Add(swScaleFilter);
 03875                mainFilters.Add($"format={outFormat}");
 3876
 3877                // sw => hw
 03878                if (doCuTonemap)
 3879                {
 03880                    mainFilters.Add("hwupload=derive_device=cuda");
 3881                }
 3882            }
 3883
 03884            if (isNvDecoder)
 3885            {
 3886                // INPUT cuda surface(vram)
 3887                // hw deint
 03888                if (doDeintH2645)
 3889                {
 03890                    var deintFilter = GetHwDeinterlaceFilter(state, options, "cuda");
 03891                    mainFilters.Add(deintFilter);
 3892                }
 3893
 3894                // hw transpose
 03895                if (doCuTranspose)
 3896                {
 03897                    mainFilters.Add($"transpose_cuda=dir={transposeDir}");
 3898                }
 3899
 03900                var isRext = IsVideoStreamHevcRext(state);
 03901                var outFormat = doCuTonemap ? (isRext ? "p010" : string.Empty) : "yuv420p";
 03902                var hwScaleFilter = GetHwScaleFilter("scale", "cuda", outFormat, false, swpInW, swpInH, reqW, reqH, reqM
 3903                // hw scale
 03904                mainFilters.Add(hwScaleFilter);
 3905            }
 3906
 3907            // hw tonemap
 03908            if (doCuTonemap)
 3909            {
 03910                var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p", isMjpegEncoder);
 03911                mainFilters.Add(tonemapFilter);
 3912            }
 3913
 03914            var memoryOutput = false;
 03915            var isUploadForCuTonemap = isSwDecoder && doCuTonemap;
 03916            if ((isNvDecoder && isSwEncoder) || (isUploadForCuTonemap && hasSubs))
 3917            {
 03918                memoryOutput = true;
 3919
 3920                // OUTPUT yuv420p surface(memory)
 03921                mainFilters.Add("hwdownload");
 03922                mainFilters.Add("format=yuv420p");
 3923            }
 3924
 3925            // OUTPUT yuv420p surface(memory)
 03926            if (isSwDecoder && isNvencEncoder && !isUploadForCuTonemap)
 3927            {
 03928                memoryOutput = true;
 3929            }
 3930
 03931            if (memoryOutput)
 3932            {
 3933                // text subtitles
 03934                if (hasTextSubs)
 3935                {
 03936                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03937                    mainFilters.Add(textSubtitlesFilter);
 3938                }
 3939            }
 3940
 3941            // OUTPUT cuda(yuv420p) surface(vram)
 3942
 3943            /* Make sub and overlay filters for subtitle stream */
 03944            var subFilters = new List<string>();
 03945            var overlayFilters = new List<string>();
 03946            if (isCuInCuOut)
 3947            {
 03948                if (hasSubs)
 3949                {
 03950                    if (hasGraphicalSubs)
 3951                    {
 03952                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 03953                        subFilters.Add(subPreProcFilters);
 03954                        subFilters.Add("format=yuva420p");
 3955                    }
 03956                    else if (hasTextSubs)
 3957                    {
 03958                        var framerate = state.VideoStream?.RealFrameRate;
 03959                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 3960
 3961                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 03962                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 03963                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 03964                        subFilters.Add(alphaSrcFilter);
 03965                        subFilters.Add("format=yuva420p");
 03966                        subFilters.Add(subTextSubtitlesFilter);
 3967                    }
 3968
 03969                    subFilters.Add("hwupload=derive_device=cuda");
 03970                    overlayFilters.Add("overlay_cuda=eof_action=pass:repeatlast=0");
 3971                }
 3972            }
 3973            else
 3974            {
 03975                if (hasGraphicalSubs)
 3976                {
 03977                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 03978                    subFilters.Add(subPreProcFilters);
 03979                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 3980                }
 3981            }
 3982
 03983            return (mainFilters, subFilters, overlayFilters);
 3984        }
 3985
 3986        /// <summary>
 3987        /// Gets the parameter of AMD AMF filter chain.
 3988        /// </summary>
 3989        /// <param name="state">Encoding state.</param>
 3990        /// <param name="options">Encoding options.</param>
 3991        /// <param name="vidEncoder">Video encoder to use.</param>
 3992        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3993        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVidFilterChain(
 3994            EncodingJobInfo state,
 3995            EncodingOptions options,
 3996            string vidEncoder)
 3997        {
 03998            if (options.HardwareAccelerationType != HardwareAccelerationType.amf)
 3999            {
 04000                return (null, null, null);
 4001            }
 4002
 04003            var isWindows = OperatingSystem.IsWindows();
 04004            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04005            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04006            var isSwEncoder = !vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04007            var isAmfDx11OclSupported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va") && IsOpenclFullSupported()
 4008
 4009            // legacy d3d11va pipeline(copy-back)
 04010            if ((isSwDecoder && isSwEncoder)
 04011                || !isAmfDx11OclSupported
 04012                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4013            {
 04014                return GetSwVidFilterChain(state, options, vidEncoder);
 4015            }
 4016
 4017            // preferred d3d11va + opencl filters + amf pipeline
 04018            return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4019        }
 4020
 4021        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdDx11VidFiltersPref
 4022            EncodingJobInfo state,
 4023            EncodingOptions options,
 4024            string vidDecoder,
 4025            string vidEncoder)
 4026        {
 04027            var inW = state.VideoStream?.Width;
 04028            var inH = state.VideoStream?.Height;
 04029            var reqW = state.BaseRequest.Width;
 04030            var reqH = state.BaseRequest.Height;
 04031            var reqMaxW = state.BaseRequest.MaxWidth;
 04032            var reqMaxH = state.BaseRequest.MaxHeight;
 04033            var threeDFormat = state.MediaSource.Video3DFormat;
 4034
 04035            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04036            var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04037            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04038            var isSwEncoder = !isAmfEncoder;
 04039            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04040            var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder;
 4041
 04042            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04043            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04044            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04045            var doOclTonemap = IsHwTonemapAvailable(state, options);
 4046
 04047            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04048            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04049            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04050            var hasAssSubs = hasSubs
 04051                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04052                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04053            var subW = state.SubtitleStream?.Width;
 04054            var subH = state.SubtitleStream?.Height;
 4055
 04056            var rotation = state.VideoStream?.Rotation ?? 0;
 04057            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04058            var doOclTranspose = !string.IsNullOrEmpty(transposeDir)
 04059                && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TransposeOpenclReversal);
 04060            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isD3d11vaDecoder && doOclTranspose));
 04061            var swpInW = swapWAndH ? inH : inW;
 04062            var swpInH = swapWAndH ? inW : inH;
 4063
 4064            /* Make main filters for video stream */
 04065            var mainFilters = new List<string>();
 4066
 04067            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 4068
 04069            if (isSwDecoder)
 4070            {
 4071                // INPUT sw surface(memory)
 4072                // sw deint
 04073                if (doDeintH2645)
 4074                {
 04075                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04076                    mainFilters.Add(swDeintFilter);
 4077                }
 4078
 04079                var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
 04080                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 4081                // sw scale
 04082                mainFilters.Add(swScaleFilter);
 04083                mainFilters.Add($"format={outFormat}");
 4084
 4085                // keep video at memory except ocl tonemap,
 4086                // since the overhead caused by hwupload >>> using sw filter.
 4087                // sw => hw
 04088                if (doOclTonemap)
 4089                {
 04090                    mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=24");
 04091                    mainFilters.Add("format=d3d11");
 04092                    mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4093                }
 4094            }
 4095
 04096            if (isD3d11vaDecoder)
 4097            {
 4098                // INPUT d3d11 surface(vram)
 4099                // map from d3d11va to opencl via d3d11-opencl interop.
 04100                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4101
 4102                // hw deint <= TODO: finish the 'yadif_opencl' filter
 4103
 4104                // hw transpose
 04105                if (doOclTranspose)
 4106                {
 04107                    mainFilters.Add($"transpose_opencl=dir={transposeDir}");
 4108                }
 4109
 04110                var outFormat = doOclTonemap ? string.Empty : "nv12";
 04111                var hwScaleFilter = GetHwScaleFilter("scale", "opencl", outFormat, false, swpInW, swpInH, reqW, reqH, re
 4112                // hw scale
 04113                mainFilters.Add(hwScaleFilter);
 4114            }
 4115
 4116            // hw tonemap
 04117            if (doOclTonemap)
 4118            {
 04119                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04120                mainFilters.Add(tonemapFilter);
 4121            }
 4122
 04123            var memoryOutput = false;
 04124            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04125            if (isD3d11vaDecoder && isSwEncoder)
 4126            {
 04127                memoryOutput = true;
 4128
 4129                // OUTPUT nv12 surface(memory)
 4130                // prefer hwmap to hwdownload on opencl.
 04131                var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap=mode=read";
 04132                mainFilters.Add(hwTransferFilter);
 04133                mainFilters.Add("format=nv12");
 4134            }
 4135
 4136            // OUTPUT yuv420p surface
 04137            if (isSwDecoder && isAmfEncoder && !isUploadForOclTonemap)
 4138            {
 04139                memoryOutput = true;
 4140            }
 4141
 04142            if (memoryOutput)
 4143            {
 4144                // text subtitles
 04145                if (hasTextSubs)
 4146                {
 04147                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04148                    mainFilters.Add(textSubtitlesFilter);
 4149                }
 4150            }
 4151
 04152            if ((isDxInDxOut || isUploadForOclTonemap) && !hasSubs)
 4153            {
 4154                // OUTPUT d3d11(nv12) surface(vram)
 4155                // reverse-mapping via d3d11-opencl interop.
 04156                mainFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04157                mainFilters.Add("format=d3d11");
 4158            }
 4159
 4160            /* Make sub and overlay filters for subtitle stream */
 04161            var subFilters = new List<string>();
 04162            var overlayFilters = new List<string>();
 04163            if (isDxInDxOut || isUploadForOclTonemap)
 4164            {
 04165                if (hasSubs)
 4166                {
 04167                    if (hasGraphicalSubs)
 4168                    {
 04169                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04170                        subFilters.Add(subPreProcFilters);
 04171                        subFilters.Add("format=yuva420p");
 4172                    }
 04173                    else if (hasTextSubs)
 4174                    {
 04175                        var framerate = state.VideoStream?.RealFrameRate;
 04176                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4177
 4178                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04179                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04180                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04181                        subFilters.Add(alphaSrcFilter);
 04182                        subFilters.Add("format=yuva420p");
 04183                        subFilters.Add(subTextSubtitlesFilter);
 4184                    }
 4185
 04186                    subFilters.Add("hwupload=derive_device=opencl");
 04187                    overlayFilters.Add("overlay_opencl=eof_action=pass:repeatlast=0");
 04188                    overlayFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04189                    overlayFilters.Add("format=d3d11");
 4190                }
 4191            }
 04192            else if (memoryOutput)
 4193            {
 04194                if (hasGraphicalSubs)
 4195                {
 04196                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04197                    subFilters.Add(subPreProcFilters);
 04198                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4199                }
 4200            }
 4201
 04202            return (mainFilters, subFilters, overlayFilters);
 4203        }
 4204
 4205        /// <summary>
 4206        /// Gets the parameter of Intel QSV filter chain.
 4207        /// </summary>
 4208        /// <param name="state">Encoding state.</param>
 4209        /// <param name="options">Encoding options.</param>
 4210        /// <param name="vidEncoder">Video encoder to use.</param>
 4211        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4212        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVidFilterChain(
 4213            EncodingJobInfo state,
 4214            EncodingOptions options,
 4215            string vidEncoder)
 4216        {
 04217            if (options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 4218            {
 04219                return (null, null, null);
 4220            }
 4221
 04222            var isWindows = OperatingSystem.IsWindows();
 04223            var isLinux = OperatingSystem.IsLinux();
 04224            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04225            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04226            var isSwEncoder = !vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04227            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 04228            var isIntelDx11OclSupported = isWindows
 04229                && _mediaEncoder.SupportsHwaccel("d3d11va")
 04230                && isQsvOclSupported;
 04231            var isIntelVaapiOclSupported = isLinux
 04232                && IsVaapiSupported(state)
 04233                && isQsvOclSupported;
 4234
 4235            // legacy qsv pipeline(copy-back)
 04236            if ((isSwDecoder && isSwEncoder)
 04237                || (!isIntelVaapiOclSupported && !isIntelDx11OclSupported)
 04238                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4239            {
 04240                return GetSwVidFilterChain(state, options, vidEncoder);
 4241            }
 4242
 4243            // preferred qsv(vaapi) + opencl filters pipeline
 04244            if (isIntelVaapiOclSupported)
 4245            {
 04246                return GetIntelQsvVaapiVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4247            }
 4248
 4249            // preferred qsv(d3d11) + opencl filters pipeline
 04250            if (isIntelDx11OclSupported)
 4251            {
 04252                return GetIntelQsvDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4253            }
 4254
 04255            return (null, null, null);
 4256        }
 4257
 4258        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvDx11VidFilter
 4259            EncodingJobInfo state,
 4260            EncodingOptions options,
 4261            string vidDecoder,
 4262            string vidEncoder)
 4263        {
 04264            var inW = state.VideoStream?.Width;
 04265            var inH = state.VideoStream?.Height;
 04266            var reqW = state.BaseRequest.Width;
 04267            var reqH = state.BaseRequest.Height;
 04268            var reqMaxW = state.BaseRequest.MaxWidth;
 04269            var reqMaxH = state.BaseRequest.MaxHeight;
 04270            var threeDFormat = state.MediaSource.Video3DFormat;
 4271
 04272            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04273            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04274            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04275            var isHwDecoder = isD3d11vaDecoder || isQsvDecoder;
 04276            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04277            var isSwEncoder = !isQsvEncoder;
 04278            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04279            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4280
 04281            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04282            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04283            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04284            var doVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04285            var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options);
 04286            var doTonemap = doVppTonemap || doOclTonemap;
 4287
 04288            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04289            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04290            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04291            var hasAssSubs = hasSubs
 04292                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04293                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04294            var subW = state.SubtitleStream?.Width;
 04295            var subH = state.SubtitleStream?.Height;
 4296
 04297            var rotation = state.VideoStream?.Rotation ?? 0;
 04298            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04299            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04300            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isD3d11vaDecoder || isQsvDecoder) && doVppTran
 04301            var swpInW = swapWAndH ? inH : inW;
 04302            var swpInH = swapWAndH ? inW : inH;
 4303
 4304            /* Make main filters for video stream */
 04305            var mainFilters = new List<string>();
 4306
 04307            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4308
 04309            if (isSwDecoder)
 4310            {
 4311                // INPUT sw surface(memory)
 4312                // sw deint
 04313                if (doDeintH2645)
 4314                {
 04315                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04316                    mainFilters.Add(swDeintFilter);
 4317                }
 4318
 04319                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04320                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04321                if (isMjpegEncoder && !doOclTonemap)
 4322                {
 4323                    // sw decoder + hw mjpeg encoder
 04324                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4325                }
 4326
 4327                // sw scale
 04328                mainFilters.Add(swScaleFilter);
 04329                mainFilters.Add($"format={outFormat}");
 4330
 4331                // keep video at memory except ocl tonemap,
 4332                // since the overhead caused by hwupload >>> using sw filter.
 4333                // sw => hw
 04334                if (doOclTonemap)
 4335                {
 04336                    mainFilters.Add("hwupload=derive_device=opencl");
 4337                }
 4338            }
 04339            else if (isD3d11vaDecoder || isQsvDecoder)
 4340            {
 04341                var isRext = IsVideoStreamHevcRext(state);
 04342                var twoPassVppTonemap = false;
 04343                var doVppFullRangeOut = isMjpegEncoder
 04344                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04345                var doVppScaleModeHq = isMjpegEncoder
 04346                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 04347                var doVppProcamp = false;
 04348                var procampParams = string.Empty;
 04349                var procampParamsString = string.Empty;
 04350                if (doVppTonemap)
 4351                {
 04352                    if (isRext)
 4353                    {
 4354                        // VPP tonemap requires p010 input
 04355                        twoPassVppTonemap = true;
 4356                    }
 4357
 04358                    if (options.VppTonemappingBrightness != 0
 04359                        && options.VppTonemappingBrightness >= -100
 04360                        && options.VppTonemappingBrightness <= 100)
 4361                    {
 04362                        procampParamsString += ":brightness={0}";
 04363                        twoPassVppTonemap = doVppProcamp = true;
 4364                    }
 4365
 04366                    if (options.VppTonemappingContrast > 1
 04367                        && options.VppTonemappingContrast <= 10)
 4368                    {
 04369                        procampParamsString += ":contrast={1}";
 04370                        twoPassVppTonemap = doVppProcamp = true;
 4371                    }
 4372
 04373                    if (doVppProcamp)
 4374                    {
 04375                        procampParamsString += ":procamp=1:async_depth=2";
 04376                        procampParams = string.Format(
 04377                            CultureInfo.InvariantCulture,
 04378                            procampParamsString,
 04379                            options.VppTonemappingBrightness,
 04380                            options.VppTonemappingContrast);
 4381                    }
 4382                }
 4383
 04384                var outFormat = doOclTonemap ? ((doVppTranspose || isRext) ? "p010" : string.Empty) : "nv12";
 04385                outFormat = twoPassVppTonemap ? "p010" : outFormat;
 4386
 04387                var swapOutputWandH = doVppTranspose && swapWAndH;
 04388                var hwScaleFilter = GetHwScaleFilter("vpp", "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, req
 4389
 04390                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose)
 4391                {
 04392                    hwScaleFilter += $":transpose={transposeDir}";
 4393                }
 4394
 04395                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4396                {
 04397                    hwScaleFilter += (doVppFullRangeOut && !doOclTonemap) ? ":out_range=pc" : string.Empty;
 04398                    hwScaleFilter += doVppScaleModeHq ? ":scale_mode=hq" : string.Empty;
 4399                }
 4400
 04401                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTonemap)
 4402                {
 04403                    hwScaleFilter += doVppProcamp ? procampParams : (twoPassVppTonemap ? string.Empty : ":tonemap=1");
 4404                }
 4405
 04406                if (isD3d11vaDecoder)
 4407                {
 04408                    if (!string.IsNullOrEmpty(hwScaleFilter) || doDeintH2645)
 4409                    {
 4410                        // INPUT d3d11 surface(vram)
 4411                        // map from d3d11va to qsv.
 04412                        mainFilters.Add("hwmap=derive_device=qsv");
 4413                    }
 4414                }
 4415
 4416                // hw deint
 04417                if (doDeintH2645)
 4418                {
 04419                    var deintFilter = GetHwDeinterlaceFilter(state, options, "qsv");
 04420                    mainFilters.Add(deintFilter);
 4421                }
 4422
 4423                // hw transpose & scale & tonemap(w/o procamp)
 04424                mainFilters.Add(hwScaleFilter);
 4425
 4426                // hw tonemap(w/ procamp)
 04427                if (doVppTonemap && twoPassVppTonemap)
 4428                {
 04429                    mainFilters.Add("vpp_qsv=tonemap=1:format=nv12:async_depth=2");
 4430                }
 4431
 4432                // force bt709 just in case vpp tonemap is not triggered or using MSDK instead of VPL.
 04433                if (doVppTonemap)
 4434                {
 04435                    mainFilters.Add(GetOverwriteColorPropertiesParam(state, false));
 4436                }
 4437            }
 4438
 04439            if (doOclTonemap && isHwDecoder)
 4440            {
 4441                // map from qsv to opencl via qsv(d3d11)-opencl interop.
 04442                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4443            }
 4444
 4445            // hw tonemap
 04446            if (doOclTonemap)
 4447            {
 04448                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04449                mainFilters.Add(tonemapFilter);
 4450            }
 4451
 04452            var memoryOutput = false;
 04453            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04454            var isHwmapUsable = isSwEncoder && doOclTonemap;
 04455            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4456            {
 04457                memoryOutput = true;
 4458
 4459                // OUTPUT nv12 surface(memory)
 4460                // prefer hwmap to hwdownload on opencl.
 4461                // qsv hwmap is not fully implemented for the time being.
 04462                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04463                mainFilters.Add("format=nv12");
 4464            }
 4465
 4466            // OUTPUT nv12 surface(memory)
 04467            if (isSwDecoder && isQsvEncoder)
 4468            {
 04469                memoryOutput = true;
 4470            }
 4471
 04472            if (memoryOutput)
 4473            {
 4474                // text subtitles
 04475                if (hasTextSubs)
 4476                {
 04477                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04478                    mainFilters.Add(textSubtitlesFilter);
 4479                }
 4480            }
 4481
 04482            if (isQsvInQsvOut && doOclTonemap)
 4483            {
 4484                // OUTPUT qsv(nv12) surface(vram)
 4485                // reverse-mapping via qsv(d3d11)-opencl interop.
 04486                mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1");
 04487                mainFilters.Add("format=qsv");
 4488            }
 4489
 4490            /* Make sub and overlay filters for subtitle stream */
 04491            var subFilters = new List<string>();
 04492            var overlayFilters = new List<string>();
 04493            if (isQsvInQsvOut)
 4494            {
 04495                if (hasSubs)
 4496                {
 04497                    if (hasGraphicalSubs)
 4498                    {
 4499                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04500                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04501                        subFilters.Add(subPreProcFilters);
 04502                        subFilters.Add("format=bgra");
 4503                    }
 04504                    else if (hasTextSubs)
 4505                    {
 04506                        var framerate = state.VideoStream?.RealFrameRate;
 04507                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4508
 4509                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 04510                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04511                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04512                        subFilters.Add(alphaSrcFilter);
 04513                        subFilters.Add("format=bgra");
 04514                        subFilters.Add(subTextSubtitlesFilter);
 4515                    }
 4516
 4517                    // qsv requires a fixed pool size.
 4518                    // default to 64 otherwise it will fail on certain iGPU.
 04519                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4520
 04521                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04522                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04523                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04524                        : string.Empty;
 04525                    var overlayQsvFilter = string.Format(
 04526                        CultureInfo.InvariantCulture,
 04527                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04528                        overlaySize);
 04529                    overlayFilters.Add(overlayQsvFilter);
 4530                }
 4531            }
 04532            else if (memoryOutput)
 4533            {
 04534                if (hasGraphicalSubs)
 4535                {
 04536                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04537                    subFilters.Add(subPreProcFilters);
 04538                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4539                }
 4540            }
 4541
 04542            return (mainFilters, subFilters, overlayFilters);
 4543        }
 4544
 4545        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvVaapiVidFilte
 4546            EncodingJobInfo state,
 4547            EncodingOptions options,
 4548            string vidDecoder,
 4549            string vidEncoder)
 4550        {
 04551            var inW = state.VideoStream?.Width;
 04552            var inH = state.VideoStream?.Height;
 04553            var reqW = state.BaseRequest.Width;
 04554            var reqH = state.BaseRequest.Height;
 04555            var reqMaxW = state.BaseRequest.MaxWidth;
 04556            var reqMaxH = state.BaseRequest.MaxHeight;
 04557            var threeDFormat = state.MediaSource.Video3DFormat;
 4558
 04559            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04560            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04561            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04562            var isHwDecoder = isVaapiDecoder || isQsvDecoder;
 04563            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04564            var isSwEncoder = !isQsvEncoder;
 04565            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04566            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4567
 04568            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04569            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04570            var doVaVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04571            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04572            var doTonemap = doVaVppTonemap || doOclTonemap;
 04573            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4574
 04575            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04576            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04577            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04578            var hasAssSubs = hasSubs
 04579                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04580                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04581            var subW = state.SubtitleStream?.Width;
 04582            var subH = state.SubtitleStream?.Height;
 4583
 04584            var rotation = state.VideoStream?.Rotation ?? 0;
 04585            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04586            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04587            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isVaapiDecoder || isQsvDecoder) && doVppTransp
 04588            var swpInW = swapWAndH ? inH : inW;
 04589            var swpInH = swapWAndH ? inW : inH;
 4590
 4591            /* Make main filters for video stream */
 04592            var mainFilters = new List<string>();
 4593
 04594            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4595
 04596            if (isSwDecoder)
 4597            {
 4598                // INPUT sw surface(memory)
 4599                // sw deint
 04600                if (doDeintH2645)
 4601                {
 04602                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04603                    mainFilters.Add(swDeintFilter);
 4604                }
 4605
 04606                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04607                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04608                if (isMjpegEncoder && !doOclTonemap)
 4609                {
 4610                    // sw decoder + hw mjpeg encoder
 04611                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4612                }
 4613
 4614                // sw scale
 04615                mainFilters.Add(swScaleFilter);
 04616                mainFilters.Add($"format={outFormat}");
 4617
 4618                // keep video at memory except ocl tonemap,
 4619                // since the overhead caused by hwupload >>> using sw filter.
 4620                // sw => hw
 04621                if (doOclTonemap)
 4622                {
 04623                    mainFilters.Add("hwupload=derive_device=opencl");
 4624                }
 4625            }
 04626            else if (isVaapiDecoder || isQsvDecoder)
 4627            {
 04628                var hwFilterSuffix = isVaapiDecoder ? "vaapi" : "qsv";
 04629                var isRext = IsVideoStreamHevcRext(state);
 04630                var doVppFullRangeOut = isMjpegEncoder
 04631                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04632                var doVppScaleModeHq = isMjpegEncoder
 04633                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 4634
 4635                // INPUT vaapi/qsv surface(vram)
 4636                // hw deint
 04637                if (doDeintH2645)
 4638                {
 04639                    var deintFilter = GetHwDeinterlaceFilter(state, options, hwFilterSuffix);
 04640                    mainFilters.Add(deintFilter);
 4641                }
 4642
 4643                // hw transpose(vaapi vpp)
 04644                if (isVaapiDecoder && doVppTranspose)
 4645                {
 04646                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 4647                }
 4648
 04649                var outFormat = doTonemap ? (((isQsvDecoder && doVppTranspose) || isRext) ? "p010" : string.Empty) : "nv
 04650                var swapOutputWandH = isQsvDecoder && doVppTranspose && swapWAndH;
 04651                var hwScalePrefix = isQsvDecoder ? "vpp" : "scale";
 04652                var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, hwFilterSuffix, outFormat, swapOutputWandH, swpInW, 
 4653
 04654                if (!string.IsNullOrEmpty(hwScaleFilter) && isQsvDecoder && doVppTranspose)
 4655                {
 04656                    hwScaleFilter += $":transpose={transposeDir}";
 4657                }
 4658
 04659                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4660                {
 04661                    hwScaleFilter += ((isQsvDecoder && !doVppFullRangeOut) || doOclTonemap) ? string.Empty : ":out_range
 04662                    hwScaleFilter += isQsvDecoder ? (doVppScaleModeHq ? ":scale_mode=hq" : string.Empty) : ":mode=hq";
 4663                }
 4664
 4665                // allocate extra pool sizes for vaapi vpp scale
 04666                if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder)
 4667                {
 04668                    hwScaleFilter += ":extra_hw_frames=24";
 4669                }
 4670
 4671                // hw transpose(qsv vpp) & scale
 04672                mainFilters.Add(hwScaleFilter);
 4673            }
 4674
 4675            // vaapi vpp tonemap
 04676            if (doVaVppTonemap && isHwDecoder)
 4677            {
 04678                if (isQsvDecoder)
 4679                {
 4680                    // map from qsv to vaapi.
 04681                    mainFilters.Add("hwmap=derive_device=vaapi");
 04682                    mainFilters.Add("format=vaapi");
 4683                }
 4684
 04685                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 04686                mainFilters.Add(tonemapFilter);
 4687
 04688                if (isQsvDecoder)
 4689                {
 4690                    // map from vaapi to qsv.
 04691                    mainFilters.Add("hwmap=derive_device=qsv");
 04692                    mainFilters.Add("format=qsv");
 4693                }
 4694            }
 4695
 04696            if (doOclTonemap && isHwDecoder)
 4697            {
 4698                // map from qsv to opencl via qsv(vaapi)-opencl interop.
 04699                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4700            }
 4701
 4702            // ocl tonemap
 04703            if (doOclTonemap)
 4704            {
 04705                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04706                mainFilters.Add(tonemapFilter);
 4707            }
 4708
 04709            var memoryOutput = false;
 04710            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04711            var isHwmapUsable = isSwEncoder && (doOclTonemap || isVaapiDecoder);
 04712            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4713            {
 04714                memoryOutput = true;
 4715
 4716                // OUTPUT nv12 surface(memory)
 4717                // prefer hwmap to hwdownload on opencl/vaapi.
 4718                // qsv hwmap is not fully implemented for the time being.
 04719                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04720                mainFilters.Add("format=nv12");
 4721            }
 4722
 4723            // OUTPUT nv12 surface(memory)
 04724            if (isSwDecoder && isQsvEncoder)
 4725            {
 04726                memoryOutput = true;
 4727            }
 4728
 04729            if (memoryOutput)
 4730            {
 4731                // text subtitles
 04732                if (hasTextSubs)
 4733                {
 04734                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04735                    mainFilters.Add(textSubtitlesFilter);
 4736                }
 4737            }
 4738
 04739            if (isQsvInQsvOut)
 4740            {
 04741                if (doOclTonemap)
 4742                {
 4743                    // OUTPUT qsv(nv12) surface(vram)
 4744                    // reverse-mapping via qsv(vaapi)-opencl interop.
 4745                    // add extra pool size to avoid the 'cannot allocate memory' error on hevc_qsv.
 04746                    mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1:extra_hw_frames=16");
 04747                    mainFilters.Add("format=qsv");
 4748                }
 04749                else if (isVaapiDecoder)
 4750                {
 04751                    mainFilters.Add("hwmap=derive_device=qsv");
 04752                    mainFilters.Add("format=qsv");
 4753                }
 4754            }
 4755
 4756            /* Make sub and overlay filters for subtitle stream */
 04757            var subFilters = new List<string>();
 04758            var overlayFilters = new List<string>();
 04759            if (isQsvInQsvOut)
 4760            {
 04761                if (hasSubs)
 4762                {
 04763                    if (hasGraphicalSubs)
 4764                    {
 4765                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04766                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04767                        subFilters.Add(subPreProcFilters);
 04768                        subFilters.Add("format=bgra");
 4769                    }
 04770                    else if (hasTextSubs)
 4771                    {
 04772                        var framerate = state.VideoStream?.RealFrameRate;
 04773                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4774
 04775                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04776                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04777                        subFilters.Add(alphaSrcFilter);
 04778                        subFilters.Add("format=bgra");
 04779                        subFilters.Add(subTextSubtitlesFilter);
 4780                    }
 4781
 4782                    // qsv requires a fixed pool size.
 4783                    // default to 64 otherwise it will fail on certain iGPU.
 04784                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4785
 04786                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04787                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04788                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04789                        : string.Empty;
 04790                    var overlayQsvFilter = string.Format(
 04791                        CultureInfo.InvariantCulture,
 04792                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04793                        overlaySize);
 04794                    overlayFilters.Add(overlayQsvFilter);
 4795                }
 4796            }
 04797            else if (memoryOutput)
 4798            {
 04799                if (hasGraphicalSubs)
 4800                {
 04801                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04802                    subFilters.Add(subPreProcFilters);
 04803                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4804                }
 4805            }
 4806
 04807            return (mainFilters, subFilters, overlayFilters);
 4808        }
 4809
 4810        /// <summary>
 4811        /// Gets the parameter of Intel/AMD VAAPI filter chain.
 4812        /// </summary>
 4813        /// <param name="state">Encoding state.</param>
 4814        /// <param name="options">Encoding options.</param>
 4815        /// <param name="vidEncoder">Video encoder to use.</param>
 4816        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4817        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiVidFilterChain(
 4818            EncodingJobInfo state,
 4819            EncodingOptions options,
 4820            string vidEncoder)
 4821        {
 04822            if (options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 4823            {
 04824                return (null, null, null);
 4825            }
 4826
 04827            var isLinux = OperatingSystem.IsLinux();
 04828            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04829            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04830            var isSwEncoder = !vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04831            var isVaapiFullSupported = isLinux && IsVaapiSupported(state) && IsVaapiFullSupported();
 04832            var isVaapiOclSupported = isVaapiFullSupported && IsOpenclFullSupported();
 04833            var isVaapiVkSupported = isVaapiFullSupported && IsVulkanFullSupported();
 4834
 4835            // legacy vaapi pipeline(copy-back)
 04836            if ((isSwDecoder && isSwEncoder)
 04837                || !isVaapiOclSupported
 04838                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4839            {
 04840                var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
 4841
 04842                if (!isSwEncoder)
 4843                {
 04844                    var newfilters = new List<string>();
 04845                    var noOverlay = swFilterChain.OverlayFilters.Count == 0;
 04846                    newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
 04847                    newfilters.Add("hwupload=derive_device=vaapi");
 4848
 04849                    var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
 04850                    var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
 04851                    return (mainFilters, swFilterChain.SubFilters, overlayFilters);
 4852                }
 4853
 04854                return swFilterChain;
 4855            }
 4856
 4857            // preferred vaapi + opencl filters pipeline
 04858            if (_mediaEncoder.IsVaapiDeviceInteliHD)
 4859            {
 4860                // Intel iHD path, with extra vpp tonemap and overlay support.
 04861                return GetIntelVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4862            }
 4863
 4864            // preferred vaapi + vulkan filters pipeline
 04865            if (_mediaEncoder.IsVaapiDeviceAmd
 04866                && isVaapiVkSupported
 04867                && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 04868                && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 4869            {
 4870                // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support.
 04871                return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4872            }
 4873
 4874            // Intel i965 and Amd legacy driver path, only featuring scale and deinterlace support.
 04875            return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4876        }
 4877
 4878        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVaapiFullVidFilt
 4879            EncodingJobInfo state,
 4880            EncodingOptions options,
 4881            string vidDecoder,
 4882            string vidEncoder)
 4883        {
 04884            var inW = state.VideoStream?.Width;
 04885            var inH = state.VideoStream?.Height;
 04886            var reqW = state.BaseRequest.Width;
 04887            var reqH = state.BaseRequest.Height;
 04888            var reqMaxW = state.BaseRequest.MaxWidth;
 04889            var reqMaxH = state.BaseRequest.MaxHeight;
 04890            var threeDFormat = state.MediaSource.Video3DFormat;
 4891
 04892            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04893            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04894            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04895            var isSwEncoder = !isVaapiEncoder;
 04896            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04897            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 4898
 04899            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04900            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04901            var doVaVppTonemap = isVaapiDecoder && IsIntelVppTonemapAvailable(state, options);
 04902            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04903            var doTonemap = doVaVppTonemap || doOclTonemap;
 04904            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4905
 04906            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04907            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04908            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04909            var hasAssSubs = hasSubs
 04910                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04911                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04912            var subW = state.SubtitleStream?.Width;
 04913            var subH = state.SubtitleStream?.Height;
 4914
 04915            var rotation = state.VideoStream?.Rotation ?? 0;
 04916            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04917            var doVaVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04918            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVaVppTranspose));
 04919            var swpInW = swapWAndH ? inH : inW;
 04920            var swpInH = swapWAndH ? inW : inH;
 4921
 4922            /* Make main filters for video stream */
 04923            var mainFilters = new List<string>();
 4924
 04925            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4926
 04927            if (isSwDecoder)
 4928            {
 4929                // INPUT sw surface(memory)
 4930                // sw deint
 04931                if (doDeintH2645)
 4932                {
 04933                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04934                    mainFilters.Add(swDeintFilter);
 4935                }
 4936
 04937                var outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 04938                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04939                if (isMjpegEncoder && !doOclTonemap)
 4940                {
 4941                    // sw decoder + hw mjpeg encoder
 04942                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4943                }
 4944
 4945                // sw scale
 04946                mainFilters.Add(swScaleFilter);
 04947                mainFilters.Add($"format={outFormat}");
 4948
 4949                // keep video at memory except ocl tonemap,
 4950                // since the overhead caused by hwupload >>> using sw filter.
 4951                // sw => hw
 04952                if (doOclTonemap)
 4953                {
 04954                    mainFilters.Add("hwupload=derive_device=opencl");
 4955                }
 4956            }
 04957            else if (isVaapiDecoder)
 4958            {
 04959                var isRext = IsVideoStreamHevcRext(state);
 4960
 4961                // INPUT vaapi surface(vram)
 4962                // hw deint
 04963                if (doDeintH2645)
 4964                {
 04965                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 04966                    mainFilters.Add(deintFilter);
 4967                }
 4968
 4969                // hw transpose
 04970                if (doVaVppTranspose)
 4971                {
 04972                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 4973                }
 4974
 04975                var outFormat = doTonemap ? (isRext ? "p010" : string.Empty) : "nv12";
 04976                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, swpInW, swpInH, reqW, reqH, req
 4977
 04978                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4979                {
 04980                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 04981                    hwScaleFilter += ":mode=hq";
 4982                }
 4983
 4984                // allocate extra pool sizes for vaapi vpp
 04985                if (!string.IsNullOrEmpty(hwScaleFilter))
 4986                {
 04987                    hwScaleFilter += ":extra_hw_frames=24";
 4988                }
 4989
 4990                // hw scale
 04991                mainFilters.Add(hwScaleFilter);
 4992            }
 4993
 4994            // vaapi vpp tonemap
 04995            if (doVaVppTonemap && isVaapiDecoder)
 4996            {
 04997                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 04998                mainFilters.Add(tonemapFilter);
 4999            }
 5000
 05001            if (doOclTonemap && isVaapiDecoder)
 5002            {
 5003                // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05004                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 5005            }
 5006
 5007            // ocl tonemap
 05008            if (doOclTonemap)
 5009            {
 05010                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05011                mainFilters.Add(tonemapFilter);
 5012            }
 5013
 05014            if (doOclTonemap && isVaInVaOut)
 5015            {
 5016                // OUTPUT vaapi(nv12) surface(vram)
 5017                // reverse-mapping via vaapi-opencl interop.
 05018                mainFilters.Add("hwmap=derive_device=vaapi:mode=write:reverse=1");
 05019                mainFilters.Add("format=vaapi");
 5020            }
 5021
 05022            var memoryOutput = false;
 05023            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 05024            var isHwmapNotUsable = isUploadForOclTonemap && isVaapiEncoder;
 05025            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap)
 5026            {
 05027                memoryOutput = true;
 5028
 5029                // OUTPUT nv12 surface(memory)
 5030                // prefer hwmap to hwdownload on opencl/vaapi.
 05031                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap=mode=read");
 05032                mainFilters.Add("format=nv12");
 5033            }
 5034
 5035            // OUTPUT nv12 surface(memory)
 05036            if (isSwDecoder && isVaapiEncoder)
 5037            {
 05038                memoryOutput = true;
 5039            }
 5040
 05041            if (memoryOutput)
 5042            {
 5043                // text subtitles
 05044                if (hasTextSubs)
 5045                {
 05046                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05047                    mainFilters.Add(textSubtitlesFilter);
 5048                }
 5049            }
 5050
 05051            if (memoryOutput && isVaapiEncoder)
 5052            {
 05053                if (!hasGraphicalSubs)
 5054                {
 05055                    mainFilters.Add("hwupload_vaapi");
 5056                }
 5057            }
 5058
 5059            /* Make sub and overlay filters for subtitle stream */
 05060            var subFilters = new List<string>();
 05061            var overlayFilters = new List<string>();
 05062            if (isVaInVaOut)
 5063            {
 05064                if (hasSubs)
 5065                {
 05066                    if (hasGraphicalSubs)
 5067                    {
 5068                        // overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead
 05069                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 05070                        subFilters.Add(subPreProcFilters);
 05071                        subFilters.Add("format=bgra");
 5072                    }
 05073                    else if (hasTextSubs)
 5074                    {
 05075                        var framerate = state.VideoStream?.RealFrameRate;
 05076                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5077
 05078                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 05079                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05080                        subFilters.Add(alphaSrcFilter);
 05081                        subFilters.Add("format=bgra");
 05082                        subFilters.Add(subTextSubtitlesFilter);
 5083                    }
 5084
 05085                    subFilters.Add("hwupload=derive_device=vaapi");
 5086
 05087                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 05088                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 05089                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 05090                        : string.Empty;
 05091                    var overlayVaapiFilter = string.Format(
 05092                        CultureInfo.InvariantCulture,
 05093                        "overlay_vaapi=eof_action=pass:repeatlast=0{0}",
 05094                        overlaySize);
 05095                    overlayFilters.Add(overlayVaapiFilter);
 5096                }
 5097            }
 05098            else if (memoryOutput)
 5099            {
 05100                if (hasGraphicalSubs)
 5101                {
 05102                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05103                    subFilters.Add(subPreProcFilters);
 05104                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5105
 05106                    if (isVaapiEncoder)
 5107                    {
 05108                        overlayFilters.Add("hwupload_vaapi");
 5109                    }
 5110                }
 5111            }
 5112
 05113            return (mainFilters, subFilters, overlayFilters);
 5114        }
 5115
 5116        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVaapiFullVidFilter
 5117            EncodingJobInfo state,
 5118            EncodingOptions options,
 5119            string vidDecoder,
 5120            string vidEncoder)
 5121        {
 05122            var inW = state.VideoStream?.Width;
 05123            var inH = state.VideoStream?.Height;
 05124            var reqW = state.BaseRequest.Width;
 05125            var reqH = state.BaseRequest.Height;
 05126            var reqMaxW = state.BaseRequest.MaxWidth;
 05127            var reqMaxH = state.BaseRequest.MaxHeight;
 05128            var threeDFormat = state.MediaSource.Video3DFormat;
 5129
 05130            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05131            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05132            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05133            var isSwEncoder = !isVaapiEncoder;
 05134            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5135
 05136            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05137            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05138            var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
 05139            var doDeintH2645 = doDeintH264 || doDeintHevc;
 5140
 05141            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05142            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05143            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05144            var hasAssSubs = hasSubs
 05145                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05146                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5147
 05148            var rotation = state.VideoStream?.Rotation ?? 0;
 05149            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05150            var doVkTranspose = isVaapiDecoder && !string.IsNullOrEmpty(transposeDir);
 05151            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVkTranspose));
 05152            var swpInW = swapWAndH ? inH : inW;
 05153            var swpInH = swapWAndH ? inW : inH;
 5154
 5155            /* Make main filters for video stream */
 05156            var mainFilters = new List<string>();
 5157
 05158            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doVkTonemap));
 5159
 05160            if (isSwDecoder)
 5161            {
 5162                // INPUT sw surface(memory)
 5163                // sw deint
 05164                if (doDeintH2645)
 5165                {
 05166                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05167                    mainFilters.Add(swDeintFilter);
 5168                }
 5169
 05170                if (doVkTonemap || hasSubs)
 5171                {
 5172                    // sw => hw
 05173                    mainFilters.Add("hwupload=derive_device=vulkan");
 05174                    mainFilters.Add("format=vulkan");
 5175                }
 5176                else
 5177                {
 5178                    // sw scale
 05179                    var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW,
 05180                    mainFilters.Add(swScaleFilter);
 05181                    mainFilters.Add("format=nv12");
 5182                }
 5183            }
 05184            else if (isVaapiDecoder)
 5185            {
 5186                // INPUT vaapi surface(vram)
 05187                if (doVkTranspose || doVkTonemap || hasSubs)
 5188                {
 5189                    // map from vaapi to vulkan/drm via interop (Polaris/gfx8+).
 05190                    if (_mediaEncoder.EncoderVersion >= _minFFmpegAlteredVaVkInterop)
 5191                    {
 05192                        if (doVkTranspose || !_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5193                        {
 5194                            // disable the indirect va-drm-vk mapping since it's no longer reliable.
 05195                            mainFilters.Add("hwmap=derive_device=drm");
 05196                            mainFilters.Add("format=drm_prime");
 05197                            mainFilters.Add("hwmap=derive_device=vulkan");
 05198                            mainFilters.Add("format=vulkan");
 5199
 5200                            // workaround for libplacebo using the imported vulkan frame on gfx8.
 05201                            if (!_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5202                            {
 05203                                mainFilters.Add("scale_vulkan");
 5204                            }
 5205                        }
 05206                        else if (doVkTonemap || hasSubs)
 5207                        {
 5208                            // non ad-hoc libplacebo also accepts drm_prime direct input.
 05209                            mainFilters.Add("hwmap=derive_device=drm");
 05210                            mainFilters.Add("format=drm_prime");
 5211                        }
 5212                    }
 5213                    else // legacy va-vk mapping that works only in jellyfin-ffmpeg6
 5214                    {
 05215                        mainFilters.Add("hwmap=derive_device=vulkan");
 05216                        mainFilters.Add("format=vulkan");
 5217                    }
 5218                }
 5219                else
 5220                {
 5221                    // hw deint
 05222                    if (doDeintH2645)
 5223                    {
 05224                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05225                        mainFilters.Add(deintFilter);
 5226                    }
 5227
 5228                    // hw scale
 05229                    var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", "nv12", false, inW, inH, reqW, reqH, reqMaxW,
 5230
 05231                    if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder && !doVkTonemap)
 5232                    {
 05233                        hwScaleFilter += ":out_range=pc:mode=hq";
 5234                    }
 5235
 05236                    mainFilters.Add(hwScaleFilter);
 5237                }
 5238            }
 5239
 5240            // vk transpose
 05241            if (doVkTranspose)
 5242            {
 05243                if (string.Equals(transposeDir, "reversal", StringComparison.OrdinalIgnoreCase))
 5244                {
 05245                    mainFilters.Add("flip_vulkan");
 5246                }
 5247                else
 5248                {
 05249                    mainFilters.Add($"transpose_vulkan=dir={transposeDir}");
 5250                }
 5251            }
 5252
 5253            // vk libplacebo
 05254            if (doVkTonemap || hasSubs)
 5255            {
 05256                var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, req
 05257                mainFilters.Add(libplaceboFilter);
 05258                mainFilters.Add("format=vulkan");
 5259            }
 5260
 05261            if (doVkTonemap && !hasSubs)
 5262            {
 5263                // OUTPUT vaapi(nv12) surface(vram)
 5264                // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05265                mainFilters.Add("hwmap=derive_device=vaapi");
 05266                mainFilters.Add("format=vaapi");
 5267
 5268                // clear the surf->meta_offset and output nv12
 05269                mainFilters.Add("scale_vaapi=format=nv12");
 5270
 5271                // hw deint
 05272                if (doDeintH2645)
 5273                {
 05274                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05275                    mainFilters.Add(deintFilter);
 5276                }
 5277            }
 5278
 05279            if (!hasSubs)
 5280            {
 5281                // OUTPUT nv12 surface(memory)
 05282                if (isSwEncoder && (doVkTonemap || isVaapiDecoder))
 5283                {
 05284                    mainFilters.Add("hwdownload");
 05285                    mainFilters.Add("format=nv12");
 5286                }
 5287
 05288                if (isSwDecoder && isVaapiEncoder && !doVkTonemap)
 5289                {
 05290                    mainFilters.Add("hwupload_vaapi");
 5291                }
 5292            }
 5293
 5294            /* Make sub and overlay filters for subtitle stream */
 05295            var subFilters = new List<string>();
 05296            var overlayFilters = new List<string>();
 05297            if (hasSubs)
 5298            {
 05299                if (hasGraphicalSubs)
 5300                {
 05301                    var subW = state.SubtitleStream?.Width;
 05302                    var subH = state.SubtitleStream?.Height;
 05303                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05304                    subFilters.Add(subPreProcFilters);
 05305                    subFilters.Add("format=bgra");
 5306                }
 05307                else if (hasTextSubs)
 5308                {
 05309                    var framerate = state.VideoStream?.RealFrameRate;
 05310                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5311
 05312                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05313                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05314                    subFilters.Add(alphaSrcFilter);
 05315                    subFilters.Add("format=bgra");
 05316                    subFilters.Add(subTextSubtitlesFilter);
 5317                }
 5318
 05319                subFilters.Add("hwupload=derive_device=vulkan");
 05320                subFilters.Add("format=vulkan");
 5321
 05322                overlayFilters.Add("overlay_vulkan=eof_action=pass:repeatlast=0");
 5323
 05324                if (isSwEncoder)
 5325                {
 5326                    // OUTPUT nv12 surface(memory)
 05327                    overlayFilters.Add("scale_vulkan=format=nv12");
 05328                    overlayFilters.Add("hwdownload");
 05329                    overlayFilters.Add("format=nv12");
 5330                }
 05331                else if (isVaapiEncoder)
 5332                {
 5333                    // OUTPUT vaapi(nv12) surface(vram)
 5334                    // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05335                    overlayFilters.Add("hwmap=derive_device=vaapi");
 05336                    overlayFilters.Add("format=vaapi");
 5337
 5338                    // clear the surf->meta_offset and output nv12
 05339                    overlayFilters.Add("scale_vaapi=format=nv12");
 5340
 5341                    // hw deint
 05342                    if (doDeintH2645)
 5343                    {
 05344                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05345                        overlayFilters.Add(deintFilter);
 5346                    }
 5347                }
 5348            }
 5349
 05350            return (mainFilters, subFilters, overlayFilters);
 5351        }
 5352
 5353        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiLimitedVidFilter
 5354            EncodingJobInfo state,
 5355            EncodingOptions options,
 5356            string vidDecoder,
 5357            string vidEncoder)
 5358        {
 05359            var inW = state.VideoStream?.Width;
 05360            var inH = state.VideoStream?.Height;
 05361            var reqW = state.BaseRequest.Width;
 05362            var reqH = state.BaseRequest.Height;
 05363            var reqMaxW = state.BaseRequest.MaxWidth;
 05364            var reqMaxH = state.BaseRequest.MaxHeight;
 05365            var threeDFormat = state.MediaSource.Video3DFormat;
 5366
 05367            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05368            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05369            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05370            var isSwEncoder = !isVaapiEncoder;
 05371            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05372            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 05373            var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965;
 05374            var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd;
 5375
 05376            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05377            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05378            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05379            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5380
 05381            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05382            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05383            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 5384
 05385            var rotation = state.VideoStream?.Rotation ?? 0;
 05386            var swapWAndH = Math.Abs(rotation) == 90 && isSwDecoder;
 05387            var swpInW = swapWAndH ? inH : inW;
 05388            var swpInH = swapWAndH ? inW : inH;
 5389
 5390            /* Make main filters for video stream */
 05391            var mainFilters = new List<string>();
 5392
 05393            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5394
 05395            var outFormat = string.Empty;
 05396            if (isSwDecoder)
 5397            {
 5398                // INPUT sw surface(memory)
 5399                // sw deint
 05400                if (doDeintH2645)
 5401                {
 05402                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05403                    mainFilters.Add(swDeintFilter);
 5404                }
 5405
 05406                outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05407                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05408                if (isMjpegEncoder && !doOclTonemap)
 5409                {
 5410                    // sw decoder + hw mjpeg encoder
 05411                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5412                }
 5413
 5414                // sw scale
 05415                mainFilters.Add(swScaleFilter);
 05416                mainFilters.Add("format=" + outFormat);
 5417
 5418                // keep video at memory except ocl tonemap,
 5419                // since the overhead caused by hwupload >>> using sw filter.
 5420                // sw => hw
 05421                if (doOclTonemap)
 5422                {
 05423                    mainFilters.Add("hwupload=derive_device=opencl");
 5424                }
 5425            }
 05426            else if (isVaapiDecoder)
 5427            {
 5428                // INPUT vaapi surface(vram)
 5429                // hw deint
 05430                if (doDeintH2645)
 5431                {
 05432                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05433                    mainFilters.Add(deintFilter);
 5434                }
 5435
 05436                outFormat = doOclTonemap ? string.Empty : "nv12";
 05437                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, inW, inH, reqW, reqH, reqMaxW, 
 5438
 05439                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5440                {
 05441                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05442                    hwScaleFilter += ":mode=hq";
 5443                }
 5444
 5445                // allocate extra pool sizes for vaapi vpp
 05446                if (!string.IsNullOrEmpty(hwScaleFilter))
 5447                {
 05448                    hwScaleFilter += ":extra_hw_frames=24";
 5449                }
 5450
 5451                // hw scale
 05452                mainFilters.Add(hwScaleFilter);
 5453            }
 5454
 05455            if (doOclTonemap && isVaapiDecoder)
 5456            {
 05457                if (isi965Driver)
 5458                {
 5459                    // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05460                    mainFilters.Add("hwmap=derive_device=opencl");
 5461                }
 5462                else
 5463                {
 05464                    mainFilters.Add("hwdownload");
 05465                    mainFilters.Add("format=p010le");
 05466                    mainFilters.Add("hwupload=derive_device=opencl");
 5467                }
 5468            }
 5469
 5470            // ocl tonemap
 05471            if (doOclTonemap)
 5472            {
 05473                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05474                mainFilters.Add(tonemapFilter);
 5475            }
 5476
 05477            if (doOclTonemap && isVaInVaOut)
 5478            {
 05479                if (isi965Driver)
 5480                {
 5481                    // OUTPUT vaapi(nv12) surface(vram)
 5482                    // reverse-mapping via vaapi-opencl interop.
 05483                    mainFilters.Add("hwmap=derive_device=vaapi:reverse=1");
 05484                    mainFilters.Add("format=vaapi");
 5485                }
 5486            }
 5487
 05488            var memoryOutput = false;
 05489            var isUploadForOclTonemap = doOclTonemap && (isSwDecoder || (isVaapiDecoder && !isi965Driver));
 05490            var isHwmapNotUsable = hasGraphicalSubs || isUploadForOclTonemap;
 05491            var isHwmapForSubs = hasSubs && isVaapiDecoder;
 05492            var isHwUnmapForTextSubs = hasTextSubs && isVaInVaOut && !isUploadForOclTonemap;
 05493            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap || isHwmapForSubs)
 5494            {
 05495                memoryOutput = true;
 5496
 5497                // OUTPUT nv12 surface(memory)
 5498                // prefer hwmap to hwdownload on opencl/vaapi.
 05499                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap");
 05500                mainFilters.Add("format=nv12");
 5501            }
 5502
 5503            // OUTPUT nv12 surface(memory)
 05504            if (isSwDecoder && isVaapiEncoder)
 5505            {
 05506                memoryOutput = true;
 5507            }
 5508
 05509            if (memoryOutput)
 5510            {
 5511                // text subtitles
 05512                if (hasTextSubs)
 5513                {
 05514                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05515                    mainFilters.Add(textSubtitlesFilter);
 5516                }
 5517            }
 5518
 05519            if (isHwUnmapForTextSubs)
 5520            {
 05521                mainFilters.Add("hwmap");
 05522                mainFilters.Add("format=vaapi");
 5523            }
 05524            else if (memoryOutput && isVaapiEncoder)
 5525            {
 05526                if (!hasGraphicalSubs)
 5527                {
 05528                    mainFilters.Add("hwupload_vaapi");
 5529                }
 5530            }
 5531
 5532            /* Make sub and overlay filters for subtitle stream */
 05533            var subFilters = new List<string>();
 05534            var overlayFilters = new List<string>();
 05535            if (memoryOutput)
 5536            {
 05537                if (hasGraphicalSubs)
 5538                {
 05539                    var subW = state.SubtitleStream?.Width;
 05540                    var subH = state.SubtitleStream?.Height;
 05541                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05542                    subFilters.Add(subPreProcFilters);
 05543                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5544
 05545                    if (isVaapiEncoder)
 5546                    {
 05547                        overlayFilters.Add("hwupload_vaapi");
 5548                    }
 5549                }
 5550            }
 5551
 05552            return (mainFilters, subFilters, overlayFilters);
 5553        }
 5554
 5555        /// <summary>
 5556        /// Gets the parameter of Apple VideoToolBox filter chain.
 5557        /// </summary>
 5558        /// <param name="state">Encoding state.</param>
 5559        /// <param name="options">Encoding options.</param>
 5560        /// <param name="vidEncoder">Video encoder to use.</param>
 5561        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5562        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFilterChain(
 5563            EncodingJobInfo state,
 5564            EncodingOptions options,
 5565            string vidEncoder)
 5566        {
 05567            if (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 5568            {
 05569                return (null, null, null);
 5570            }
 5571
 5572            // ReSharper disable once InconsistentNaming
 05573            var isMacOS = OperatingSystem.IsMacOS();
 05574            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05575            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05576            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05577            var isVtFullSupported = isMacOS && IsVideoToolboxFullSupported();
 5578
 5579            // legacy videotoolbox pipeline (disable hw filters)
 05580            if (!(isVtEncoder || isVtDecoder)
 05581                || !isVtFullSupported
 05582                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5583            {
 05584                return GetSwVidFilterChain(state, options, vidEncoder);
 5585            }
 5586
 5587            // preferred videotoolbox + metal filters pipeline
 05588            return GetAppleVidFiltersPreferred(state, options, vidDecoder, vidEncoder);
 5589        }
 5590
 5591        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFiltersPrefer
 5592            EncodingJobInfo state,
 5593            EncodingOptions options,
 5594            string vidDecoder,
 5595            string vidEncoder)
 5596        {
 05597            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05598            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05599            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5600
 05601            var inW = state.VideoStream?.Width;
 05602            var inH = state.VideoStream?.Height;
 05603            var reqW = state.BaseRequest.Width;
 05604            var reqH = state.BaseRequest.Height;
 05605            var reqMaxW = state.BaseRequest.MaxWidth;
 05606            var reqMaxH = state.BaseRequest.MaxHeight;
 05607            var threeDFormat = state.MediaSource.Video3DFormat;
 5608
 05609            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05610            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05611            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05612            var doVtTonemap = IsVideoToolboxTonemapAvailable(state, options);
 05613            var doMetalTonemap = !doVtTonemap && IsHwTonemapAvailable(state, options);
 05614            var usingHwSurface = isVtDecoder && (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface);
 5615
 05616            var rotation = state.VideoStream?.Rotation ?? 0;
 05617            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05618            var doVtTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_vt");
 05619            var swapWAndH = Math.Abs(rotation) == 90 && doVtTranspose;
 05620            var swpInW = swapWAndH ? inH : inW;
 05621            var swpInH = swapWAndH ? inW : inH;
 5622
 05623            var scaleFormat = string.Empty;
 5624            // Use P010 for Metal tone mapping, otherwise force an 8bit output.
 05625            if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase))
 5626            {
 05627                if (doMetalTonemap)
 5628                {
 05629                    if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 5630                    {
 05631                        scaleFormat = "p010le";
 5632                    }
 5633                }
 5634                else
 5635                {
 05636                    scaleFormat = "nv12";
 5637                }
 5638            }
 5639
 05640            var hwScaleFilter = GetHwScaleFilter("scale", "vt", scaleFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW,
 5641
 05642            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05643            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05644            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05645            var hasAssSubs = hasSubs
 05646                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05647                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5648
 5649            /* Make main filters for video stream */
 05650            var mainFilters = new List<string>();
 5651
 5652            // hw deint
 05653            if (doDeintH2645)
 5654            {
 05655                var deintFilter = GetHwDeinterlaceFilter(state, options, "videotoolbox");
 05656                mainFilters.Add(deintFilter);
 5657            }
 5658
 5659            // hw transpose
 05660            if (doVtTranspose)
 5661            {
 05662                mainFilters.Add($"transpose_vt=dir={transposeDir}");
 5663            }
 5664
 05665            if (doVtTonemap)
 5666            {
 5667                const string VtTonemapArgs = "color_matrix=bt709:color_primaries=bt709:color_transfer=bt709";
 5668
 5669                // scale_vt can handle scaling & tonemapping in one shot, just like vpp_qsv.
 05670                hwScaleFilter = string.IsNullOrEmpty(hwScaleFilter)
 05671                    ? "scale_vt=" + VtTonemapArgs
 05672                    : hwScaleFilter + ":" + VtTonemapArgs;
 5673            }
 5674
 5675            // hw scale & vt tonemap
 05676            mainFilters.Add(hwScaleFilter);
 5677
 5678            // Metal tonemap
 05679            if (doMetalTonemap)
 5680            {
 05681                var tonemapFilter = GetHwTonemapFilter(options, "videotoolbox", "nv12", isMjpegEncoder);
 05682                mainFilters.Add(tonemapFilter);
 5683            }
 5684
 5685            /* Make sub and overlay filters for subtitle stream */
 05686            var subFilters = new List<string>();
 05687            var overlayFilters = new List<string>();
 5688
 05689            if (hasSubs)
 5690            {
 05691                if (hasGraphicalSubs)
 5692                {
 05693                    var subW = state.SubtitleStream?.Width;
 05694                    var subH = state.SubtitleStream?.Height;
 05695                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05696                    subFilters.Add(subPreProcFilters);
 05697                    subFilters.Add("format=bgra");
 5698                }
 05699                else if (hasTextSubs)
 5700                {
 05701                    var framerate = state.VideoStream?.RealFrameRate;
 05702                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5703
 05704                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05705                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05706                    subFilters.Add(alphaSrcFilter);
 05707                    subFilters.Add("format=bgra");
 05708                    subFilters.Add(subTextSubtitlesFilter);
 5709                }
 5710
 05711                subFilters.Add("hwupload");
 05712                overlayFilters.Add("overlay_videotoolbox=eof_action=pass:repeatlast=0");
 5713            }
 5714
 05715            if (usingHwSurface)
 5716            {
 05717                if (!isVtEncoder)
 5718                {
 05719                    mainFilters.Add("hwdownload");
 05720                    mainFilters.Add("format=nv12");
 5721                }
 5722
 05723                return (mainFilters, subFilters, overlayFilters);
 5724            }
 5725
 5726            // For old jellyfin-ffmpeg that has broken hwsurface, add a hwupload
 05727            var needFiltering = mainFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05728                                subFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05729                                overlayFilters.Any(f => !string.IsNullOrEmpty(f));
 05730            if (needFiltering)
 5731            {
 5732                // INPUT videotoolbox/memory surface(vram/uma)
 5733                // this will pass-through automatically if in/out format matches.
 05734                mainFilters.Insert(0, "hwupload");
 05735                mainFilters.Insert(0, "format=nv12|p010le|videotoolbox_vld");
 5736
 05737                if (!isVtEncoder)
 5738                {
 05739                    mainFilters.Add("hwdownload");
 05740                    mainFilters.Add("format=nv12");
 5741                }
 5742            }
 5743
 05744            return (mainFilters, subFilters, overlayFilters);
 5745        }
 5746
 5747        /// <summary>
 5748        /// Gets the parameter of Rockchip RKMPP/RKRGA filter chain.
 5749        /// </summary>
 5750        /// <param name="state">Encoding state.</param>
 5751        /// <param name="options">Encoding options.</param>
 5752        /// <param name="vidEncoder">Video encoder to use.</param>
 5753        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5754        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFilterChain(
 5755            EncodingJobInfo state,
 5756            EncodingOptions options,
 5757            string vidEncoder)
 5758        {
 05759            if (options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 5760            {
 05761                return (null, null, null);
 5762            }
 5763
 05764            var isLinux = OperatingSystem.IsLinux();
 05765            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05766            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05767            var isSwEncoder = !vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05768            var isRkmppOclSupported = isLinux && IsRkmppFullSupported() && IsOpenclFullSupported();
 5769
 05770            if ((isSwDecoder && isSwEncoder)
 05771                || !isRkmppOclSupported
 05772                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5773            {
 05774                return GetSwVidFilterChain(state, options, vidEncoder);
 5775            }
 5776
 5777            // preferred rkmpp + rkrga + opencl filters pipeline
 05778            if (isRkmppOclSupported)
 5779            {
 05780                return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5781            }
 5782
 05783            return (null, null, null);
 5784        }
 5785
 5786        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFiltersPrefer
 5787            EncodingJobInfo state,
 5788            EncodingOptions options,
 5789            string vidDecoder,
 5790            string vidEncoder)
 5791        {
 05792            var inW = state.VideoStream?.Width;
 05793            var inH = state.VideoStream?.Height;
 05794            var reqW = state.BaseRequest.Width;
 05795            var reqH = state.BaseRequest.Height;
 05796            var reqMaxW = state.BaseRequest.MaxWidth;
 05797            var reqMaxH = state.BaseRequest.MaxHeight;
 05798            var threeDFormat = state.MediaSource.Video3DFormat;
 5799
 05800            var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05801            var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05802            var isSwDecoder = !isRkmppDecoder;
 05803            var isSwEncoder = !isRkmppEncoder;
 05804            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05805            var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder;
 05806            var isEncoderSupportAfbc = isRkmppEncoder
 05807                && (vidEncoder.Contains("h264", StringComparison.OrdinalIgnoreCase)
 05808                    || vidEncoder.Contains("hevc", StringComparison.OrdinalIgnoreCase));
 5809
 05810            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05811            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05812            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05813            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5814
 05815            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05816            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05817            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05818            var hasAssSubs = hasSubs
 05819                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05820                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 05821            var subW = state.SubtitleStream?.Width;
 05822            var subH = state.SubtitleStream?.Height;
 5823
 05824            var rotation = state.VideoStream?.Rotation ?? 0;
 05825            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05826            var doRkVppTranspose = !string.IsNullOrEmpty(transposeDir);
 05827            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isRkmppDecoder && doRkVppTranspose));
 05828            var swpInW = swapWAndH ? inH : inW;
 05829            var swpInH = swapWAndH ? inW : inH;
 5830
 5831            /* Make main filters for video stream */
 05832            var mainFilters = new List<string>();
 5833
 05834            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5835
 05836            if (isSwDecoder)
 5837            {
 5838                // INPUT sw surface(memory)
 5839                // sw deint
 05840                if (doDeintH2645)
 5841                {
 05842                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05843                    mainFilters.Add(swDeintFilter);
 5844                }
 5845
 05846                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 05847                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05848                if (isMjpegEncoder && !doOclTonemap)
 5849                {
 5850                    // sw decoder + hw mjpeg encoder
 05851                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5852                }
 5853
 05854                if (!string.IsNullOrEmpty(swScaleFilter))
 5855                {
 05856                    swScaleFilter += ":flags=fast_bilinear";
 5857                }
 5858
 5859                // sw scale
 05860                mainFilters.Add(swScaleFilter);
 05861                mainFilters.Add($"format={outFormat}");
 5862
 5863                // keep video at memory except ocl tonemap,
 5864                // since the overhead caused by hwupload >>> using sw filter.
 5865                // sw => hw
 05866                if (doOclTonemap)
 5867                {
 05868                    mainFilters.Add("hwupload=derive_device=opencl");
 5869                }
 5870            }
 05871            else if (isRkmppDecoder)
 5872            {
 5873                // INPUT rkmpp/drm surface(gem/dma-heap)
 5874
 05875                var isFullAfbcPipeline = isEncoderSupportAfbc && isDrmInDrmOut && !doOclTonemap;
 05876                var swapOutputWandH = doRkVppTranspose && swapWAndH;
 05877                var outFormat = doOclTonemap ? "p010" : (isMjpegEncoder ? "bgra" : "nv12"); // RGA only support full ran
 05878                var hwScaleFilter = GetHwScaleFilter("vpp", "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, r
 05879                var doScaling = GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, swpInW, swpInH, reqW, re
 5880
 05881                if (!hasSubs
 05882                     || doRkVppTranspose
 05883                     || !isFullAfbcPipeline
 05884                     || !string.IsNullOrEmpty(doScaling))
 5885                {
 5886                    // RGA3 hardware only support (1/8 ~ 8) scaling in each blit operation,
 5887                    // but in Trickplay there's a case: (3840/320 == 12), enable 2pass for it
 05888                    if (!string.IsNullOrEmpty(doScaling)
 05889                        && !IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f))
 5890                    {
 5891                        // Vendor provided BSP kernel has an RGA driver bug that causes the output to be corrupted for P
 5892                        // Use NV15 instead of P010 to avoid the issue.
 5893                        // SDR inputs are using BGRA formats already which is not affected.
 05894                        var intermediateFormat = string.Equals(outFormat, "p010", StringComparison.OrdinalIgnoreCase) ? 
 05895                        var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={intermediateFormat}:force_d
 05896                        mainFilters.Add(hwScaleFilterFirstPass);
 5897                    }
 5898
 05899                    if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose)
 5900                    {
 05901                        hwScaleFilter += $":transpose={transposeDir}";
 5902                    }
 5903
 5904                    // try enabling AFBC to save DDR bandwidth
 05905                    if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline)
 5906                    {
 05907                        hwScaleFilter += ":afbc=1";
 5908                    }
 5909
 5910                    // hw transpose & scale
 05911                    mainFilters.Add(hwScaleFilter);
 5912                }
 5913            }
 5914
 05915            if (doOclTonemap && isRkmppDecoder)
 5916            {
 5917                // map from rkmpp/drm to opencl via drm-opencl interop.
 05918                mainFilters.Add("hwmap=derive_device=opencl");
 5919            }
 5920
 5921            // ocl tonemap
 05922            if (doOclTonemap)
 5923            {
 05924                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05925                mainFilters.Add(tonemapFilter);
 5926            }
 5927
 05928            var memoryOutput = false;
 05929            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 05930            if ((isRkmppDecoder && isSwEncoder) || isUploadForOclTonemap)
 5931            {
 05932                memoryOutput = true;
 5933
 5934                // OUTPUT nv12 surface(memory)
 05935                mainFilters.Add("hwdownload");
 05936                mainFilters.Add("format=nv12");
 5937            }
 5938
 5939            // OUTPUT nv12 surface(memory)
 05940            if (isSwDecoder && isRkmppEncoder)
 5941            {
 05942                memoryOutput = true;
 5943            }
 5944
 05945            if (memoryOutput)
 5946            {
 5947                // text subtitles
 05948                if (hasTextSubs)
 5949                {
 05950                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05951                    mainFilters.Add(textSubtitlesFilter);
 5952                }
 5953            }
 5954
 05955            if (isDrmInDrmOut)
 5956            {
 05957                if (doOclTonemap)
 5958                {
 5959                    // OUTPUT drm(nv12) surface(gem/dma-heap)
 5960                    // reverse-mapping via drm-opencl interop.
 05961                    mainFilters.Add("hwmap=derive_device=rkmpp:reverse=1");
 05962                    mainFilters.Add("format=drm_prime");
 5963                }
 5964            }
 5965
 5966            /* Make sub and overlay filters for subtitle stream */
 05967            var subFilters = new List<string>();
 05968            var overlayFilters = new List<string>();
 05969            if (isDrmInDrmOut)
 5970            {
 05971                if (hasSubs)
 5972                {
 05973                    if (hasGraphicalSubs)
 5974                    {
 05975                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 05976                        subFilters.Add(subPreProcFilters);
 05977                        subFilters.Add("format=bgra");
 5978                    }
 05979                    else if (hasTextSubs)
 5980                    {
 05981                        var framerate = state.VideoStream?.RealFrameRate;
 05982                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5983
 5984                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 05985                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 05986                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05987                        subFilters.Add(alphaSrcFilter);
 05988                        subFilters.Add("format=bgra");
 05989                        subFilters.Add(subTextSubtitlesFilter);
 5990                    }
 5991
 05992                    subFilters.Add("hwupload=derive_device=rkmpp");
 5993
 5994                    // try enabling AFBC to save DDR bandwidth
 05995                    var hwOverlayFilter = "overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12";
 05996                    if (isEncoderSupportAfbc)
 5997                    {
 05998                        hwOverlayFilter += ":afbc=1";
 5999                    }
 6000
 06001                    overlayFilters.Add(hwOverlayFilter);
 6002                }
 6003            }
 06004            else if (memoryOutput)
 6005            {
 06006                if (hasGraphicalSubs)
 6007                {
 06008                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 06009                    subFilters.Add(subPreProcFilters);
 06010                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 6011                }
 6012            }
 6013
 06014            return (mainFilters, subFilters, overlayFilters);
 6015        }
 6016
 6017        /// <summary>
 6018        /// Gets the parameter of video processing filters.
 6019        /// </summary>
 6020        /// <param name="state">Encoding state.</param>
 6021        /// <param name="options">Encoding options.</param>
 6022        /// <param name="outputVideoCodec">Video codec to use.</param>
 6023        /// <returns>The video processing filters parameter.</returns>
 6024        public string GetVideoProcessingFilterParam(
 6025            EncodingJobInfo state,
 6026            EncodingOptions options,
 6027            string outputVideoCodec)
 6028        {
 06029            var videoStream = state.VideoStream;
 06030            if (videoStream is null)
 6031            {
 06032                return string.Empty;
 6033            }
 6034
 06035            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 06036            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 06037            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 6038
 6039            List<string> mainFilters;
 6040            List<string> subFilters;
 6041            List<string> overlayFilters;
 6042
 06043            (mainFilters, subFilters, overlayFilters) = options.HardwareAccelerationType switch
 06044            {
 06045                HardwareAccelerationType.vaapi => GetVaapiVidFilterChain(state, options, outputVideoCodec),
 06046                HardwareAccelerationType.amf => GetAmdVidFilterChain(state, options, outputVideoCodec),
 06047                HardwareAccelerationType.qsv => GetIntelVidFilterChain(state, options, outputVideoCodec),
 06048                HardwareAccelerationType.nvenc => GetNvidiaVidFilterChain(state, options, outputVideoCodec),
 06049                HardwareAccelerationType.videotoolbox => GetAppleVidFilterChain(state, options, outputVideoCodec),
 06050                HardwareAccelerationType.rkmpp => GetRkmppVidFilterChain(state, options, outputVideoCodec),
 06051                _ => GetSwVidFilterChain(state, options, outputVideoCodec),
 06052            };
 6053
 06054            mainFilters?.RemoveAll(string.IsNullOrEmpty);
 06055            subFilters?.RemoveAll(string.IsNullOrEmpty);
 06056            overlayFilters?.RemoveAll(string.IsNullOrEmpty);
 6057
 06058            var framerate = GetFramerateParam(state);
 06059            if (framerate.HasValue)
 6060            {
 06061                mainFilters.Insert(0, string.Format(
 06062                    CultureInfo.InvariantCulture,
 06063                    "fps={0}",
 06064                    framerate.Value));
 6065            }
 6066
 06067            var mainStr = string.Empty;
 06068            if (mainFilters?.Count > 0)
 6069            {
 06070                mainStr = string.Format(
 06071                    CultureInfo.InvariantCulture,
 06072                    "{0}",
 06073                    string.Join(',', mainFilters));
 6074            }
 6075
 06076            if (overlayFilters?.Count == 0)
 6077            {
 6078                // -vf "scale..."
 06079                return string.IsNullOrEmpty(mainStr) ? string.Empty : " -vf \"" + mainStr + "\"";
 6080            }
 6081
 06082            if (overlayFilters?.Count > 0
 06083                && subFilters?.Count > 0
 06084                && state.SubtitleStream is not null)
 6085            {
 6086                // overlay graphical/text subtitles
 06087                var subStr = string.Format(
 06088                        CultureInfo.InvariantCulture,
 06089                        "{0}",
 06090                        string.Join(',', subFilters));
 6091
 06092                var overlayStr = string.Format(
 06093                        CultureInfo.InvariantCulture,
 06094                        "{0}",
 06095                        string.Join(',', overlayFilters));
 6096
 06097                var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
 06098                var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 06099                var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 6100
 06101                if (hasSubs)
 6102                {
 6103                    // -filter_complex "[0:s]scale=s[sub]..."
 06104                    var filterStr = string.IsNullOrEmpty(mainStr)
 06105                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]{5}\""
 06106                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6107
 06108                    if (hasTextSubs)
 6109                    {
 06110                        filterStr = string.IsNullOrEmpty(mainStr)
 06111                            ? " -filter_complex \"{4}[sub];[0:{2}][sub]{5}\""
 06112                            : " -filter_complex \"{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6113                    }
 6114
 06115                    return string.Format(
 06116                        CultureInfo.InvariantCulture,
 06117                        filterStr,
 06118                        mapPrefix,
 06119                        subtitleStreamIndex,
 06120                        videoStreamIndex,
 06121                        mainStr,
 06122                        subStr,
 06123                        overlayStr);
 6124                }
 6125            }
 6126
 06127            return string.Empty;
 6128        }
 6129
 6130        public string GetOverwriteColorPropertiesParam(EncodingJobInfo state, bool isTonemapAvailable)
 6131        {
 06132            if (isTonemapAvailable)
 6133            {
 06134                return GetInputHdrParam(state.VideoStream?.ColorTransfer);
 6135            }
 6136
 06137            return GetOutputSdrParam(null);
 6138        }
 6139
 6140        public string GetInputHdrParam(string colorTransfer)
 6141        {
 06142            if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
 6143            {
 6144                // HLG
 06145                return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
 6146            }
 6147
 6148            // HDR10
 06149            return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc";
 6150        }
 6151
 6152        public string GetOutputSdrParam(string tonemappingRange)
 6153        {
 6154            // SDR
 06155            if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
 6156            {
 06157                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
 6158            }
 6159
 06160            if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
 6161            {
 06162                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc";
 6163            }
 6164
 06165            return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
 6166        }
 6167
 6168        public static int GetVideoColorBitDepth(EncodingJobInfo state)
 6169        {
 06170            var videoStream = state.VideoStream;
 06171            if (videoStream is not null)
 6172            {
 06173                if (videoStream.BitDepth.HasValue)
 6174                {
 06175                    return videoStream.BitDepth.Value;
 6176                }
 6177
 06178                if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
 06179                    || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
 06180                    || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 06181                    || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
 6182                {
 06183                    return 8;
 6184                }
 6185
 06186                if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 06187                    || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 06188                    || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
 6189                {
 06190                    return 10;
 6191                }
 6192
 06193                if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 06194                    || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 06195                    || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
 6196                {
 06197                    return 12;
 6198                }
 6199
 06200                return 8;
 6201            }
 6202
 06203            return 0;
 6204        }
 6205
 6206        /// <summary>
 6207        /// Gets the ffmpeg option string for the hardware accelerated video decoder.
 6208        /// </summary>
 6209        /// <param name="state">The encoding job info.</param>
 6210        /// <param name="options">The encoding options.</param>
 6211        /// <returns>The option string or null if none available.</returns>
 6212        protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
 6213        {
 06214            var videoStream = state.VideoStream;
 06215            var mediaSource = state.MediaSource;
 06216            if (videoStream is null || mediaSource is null)
 6217            {
 06218                return null;
 6219            }
 6220
 6221            // HWA decoders can handle both video files and video folders.
 06222            var videoType = state.VideoType;
 06223            if (videoType != VideoType.VideoFile
 06224                && videoType != VideoType.Iso
 06225                && videoType != VideoType.Dvd
 06226                && videoType != VideoType.BluRay)
 6227            {
 06228                return null;
 6229            }
 6230
 06231            if (IsCopyCodec(state.OutputVideoCodec))
 6232            {
 06233                return null;
 6234            }
 6235
 06236            var hardwareAccelerationType = options.HardwareAccelerationType;
 6237
 06238            if (!string.IsNullOrEmpty(videoStream.Codec) && hardwareAccelerationType != HardwareAccelerationType.none)
 6239            {
 06240                var bitDepth = GetVideoColorBitDepth(state);
 6241
 6242                // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support for most platforms
 06243                if (bitDepth == 10
 06244                    && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06245                         || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
 06246                         || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)
 06247                         || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)))
 6248                {
 6249                    // RKMPP has H.264 Hi10P decoder
 06250                    bool hasHardwareHi10P = hardwareAccelerationType == HardwareAccelerationType.rkmpp;
 6251
 6252                    // VideoToolbox on Apple Silicon has H.264 Hi10P mode enabled after macOS 14.6
 06253                    if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox)
 6254                    {
 06255                        var ver = Environment.OSVersion.Version;
 06256                        var arch = RuntimeInformation.OSArchitecture;
 06257                        if (arch.Equals(Architecture.Arm64) && ver >= new Version(14, 6))
 6258                        {
 06259                            hasHardwareHi10P = true;
 6260                        }
 6261                    }
 6262
 06263                    if (!hasHardwareHi10P
 06264                        && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6265                    {
 06266                        return null;
 6267                    }
 6268                }
 6269
 06270                var decoder = hardwareAccelerationType switch
 06271                {
 06272                    HardwareAccelerationType.vaapi => GetVaapiVidDecoder(state, options, videoStream, bitDepth),
 06273                    HardwareAccelerationType.amf => GetAmfVidDecoder(state, options, videoStream, bitDepth),
 06274                    HardwareAccelerationType.qsv => GetQsvHwVidDecoder(state, options, videoStream, bitDepth),
 06275                    HardwareAccelerationType.nvenc => GetNvdecVidDecoder(state, options, videoStream, bitDepth),
 06276                    HardwareAccelerationType.videotoolbox => GetVideotoolboxVidDecoder(state, options, videoStream, bitD
 06277                    HardwareAccelerationType.rkmpp => GetRkmppVidDecoder(state, options, videoStream, bitDepth),
 06278                    _ => string.Empty
 06279                };
 6280
 06281                if (!string.IsNullOrEmpty(decoder))
 6282                {
 06283                    return decoder;
 6284                }
 6285            }
 6286
 6287            // leave blank so ffmpeg will decide
 06288            return null;
 6289        }
 6290
 6291        /// <summary>
 6292        /// Gets a hw decoder name.
 6293        /// </summary>
 6294        /// <param name="options">Encoding options.</param>
 6295        /// <param name="decoderPrefix">Decoder prefix.</param>
 6296        /// <param name="decoderSuffix">Decoder suffix.</param>
 6297        /// <param name="videoCodec">Video codec to use.</param>
 6298        /// <param name="bitDepth">Video color bit depth.</param>
 6299        /// <returns>Hardware decoder name.</returns>
 6300        public string GetHwDecoderName(EncodingOptions options, string decoderPrefix, string decoderSuffix, string video
 6301        {
 06302            if (string.IsNullOrEmpty(decoderPrefix) || string.IsNullOrEmpty(decoderSuffix))
 6303            {
 06304                return null;
 6305            }
 6306
 06307            var decoderName = decoderPrefix + '_' + decoderSuffix;
 6308
 06309            var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains
 6310
 6311            // VideoToolbox decoders have built-in SW fallback
 06312            if (bitDepth == 10
 06313                && isCodecAvailable
 06314                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6315            {
 06316                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06317                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
 06318                    && !options.EnableDecodingColorDepth10Hevc)
 6319                {
 06320                    return null;
 6321                }
 6322
 06323                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06324                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06325                    && !options.EnableDecodingColorDepth10Vp9)
 6326                {
 06327                    return null;
 6328                }
 6329            }
 6330
 06331            if (string.Equals(decoderSuffix, "cuvid", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdec
 6332            {
 06333                return null;
 6334            }
 6335
 06336            if (string.Equals(decoderSuffix, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwD
 6337            {
 06338                return null;
 6339            }
 6340
 06341            if (string.Equals(decoderSuffix, "rkmpp", StringComparison.OrdinalIgnoreCase))
 6342            {
 06343                return null;
 6344            }
 6345
 06346            return isCodecAvailable ? (" -c:v " + decoderName) : null;
 6347        }
 6348
 6349        /// <summary>
 6350        /// Gets a hwaccel type to use as a hardware decoder depending on the system.
 6351        /// </summary>
 6352        /// <param name="state">Encoding state.</param>
 6353        /// <param name="options">Encoding options.</param>
 6354        /// <param name="videoCodec">Video codec to use.</param>
 6355        /// <param name="bitDepth">Video color bit depth.</param>
 6356        /// <param name="outputHwSurface">Specifies if output hw surface.</param>
 6357        /// <returns>Hardware accelerator type.</returns>
 6358        public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, int bitDepth, bo
 6359        {
 06360            var isWindows = OperatingSystem.IsWindows();
 06361            var isLinux = OperatingSystem.IsLinux();
 06362            var isMacOS = OperatingSystem.IsMacOS();
 06363            var isD3d11Supported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va");
 06364            var isVaapiSupported = isLinux && IsVaapiSupported(state);
 06365            var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported();
 06366            var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv");
 06367            var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
 06368            var isRkmppSupported = isLinux && IsRkmppFullSupported();
 06369            var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCas
 06370            var hardwareAccelerationType = options.HardwareAccelerationType;
 6371
 06372            var ffmpegVersion = _mediaEncoder.EncoderVersion;
 6373
 6374            // Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
 06375            var isAv1 = ffmpegVersion < _minFFmpegImplicitHwaccel
 06376                && string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
 6377
 6378            // Allow profile mismatch if decoding H.264 baseline with d3d11va and vaapi hwaccels.
 06379            var profileMismatch = string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
 06380                && string.Equals(state.VideoStream?.Profile, "baseline", StringComparison.OrdinalIgnoreCase);
 6381
 6382            // Disable the extra internal copy in nvdec. We already handle it in filter chain.
 06383            var nvdecNoInternalCopy = ffmpegVersion >= _minFFmpegHwaUnsafeOutput;
 6384
 6385            // Strip the display rotation side data from the transposed fmp4 output stream.
 06386            var stripRotationData = (state.VideoStream?.Rotation ?? 0) != 0
 06387                && ffmpegVersion >= _minFFmpegDisplayRotationOption;
 06388            var stripRotationDataArgs = stripRotationData ? " -display_rotation 0" : string.Empty;
 6389
 6390            // VideoToolbox decoders have built-in SW fallback
 06391            if (isCodecAvailable
 06392                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6393            {
 06394                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06395                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase))
 6396                {
 06397                    if (IsVideoStreamHevcRext(state))
 6398                    {
 06399                        if (bitDepth <= 10 && !options.EnableDecodingColorDepth10HevcRext)
 6400                        {
 06401                            return null;
 6402                        }
 6403
 06404                        if (bitDepth == 12 && !options.EnableDecodingColorDepth12HevcRext)
 6405                        {
 06406                            return null;
 6407                        }
 6408
 06409                        if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06410                            && !_mediaEncoder.IsVaapiDeviceInteliHD)
 6411                        {
 06412                            return null;
 6413                        }
 6414                    }
 06415                    else if (bitDepth == 10 && !options.EnableDecodingColorDepth10Hevc)
 6416                    {
 06417                        return null;
 6418                    }
 6419                }
 6420
 06421                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06422                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06423                    && bitDepth == 10
 06424                    && !options.EnableDecodingColorDepth10Vp9)
 6425                {
 06426                    return null;
 6427                }
 6428            }
 6429
 6430            // Intel qsv/d3d11va/vaapi
 06431            if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 6432            {
 06433                if (options.PreferSystemNativeHwDecoder)
 6434                {
 06435                    if (isVaapiSupported && isCodecAvailable)
 6436                    {
 06437                        return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + st
 06438                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " 
 6439                    }
 6440
 06441                    if (isD3d11Supported && isCodecAvailable)
 6442                    {
 06443                        return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + 
 06444                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 
 6445                    }
 6446                }
 6447                else
 6448                {
 06449                    if (isQsvSupported && isCodecAvailable)
 6450                    {
 06451                        return " -hwaccel qsv" + (outputHwSurface ? " -hwaccel_output_format qsv -noautorotate" + stripR
 6452                    }
 6453                }
 6454            }
 6455
 6456            // Nvidia cuda
 06457            if (hardwareAccelerationType == HardwareAccelerationType.nvenc)
 6458            {
 06459                if (isCudaSupported && isCodecAvailable)
 6460                {
 06461                    if (options.EnableEnhancedNvdecDecoder)
 6462                    {
 6463                        // set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support.
 06464                        return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stri
 06465                            + (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + 
 6466                    }
 6467
 6468                    // cuvid decoder doesn't have threading issue.
 06469                    return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stripRot
 6470                }
 6471            }
 6472
 6473            // Amd d3d11va
 06474            if (hardwareAccelerationType == HardwareAccelerationType.amf)
 6475            {
 06476                if (isD3d11Supported && isCodecAvailable)
 6477                {
 06478                    return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + stri
 06479                        + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v
 6480                }
 6481            }
 6482
 6483            // Vaapi
 06484            if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06485                && isVaapiSupported
 06486                && isCodecAvailable)
 6487            {
 06488                return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + stripRotat
 06489                    + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1
 6490            }
 6491
 6492            // Apple videotoolbox
 06493            if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06494                && isVideotoolboxSupported
 06495                && isCodecAvailable)
 6496            {
 06497                return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string
 6498            }
 6499
 6500            // Rockchip rkmpp
 06501            if (hardwareAccelerationType == HardwareAccelerationType.rkmpp
 06502                && isRkmppSupported
 06503                && isCodecAvailable)
 6504            {
 06505                return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime -noautorotate" + stripR
 6506            }
 6507
 06508            return null;
 6509        }
 6510
 6511        public string GetQsvHwVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6512        {
 06513            var isWindows = OperatingSystem.IsWindows();
 06514            var isLinux = OperatingSystem.IsLinux();
 6515
 06516            if ((!isWindows && !isLinux)
 06517                || options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 6518            {
 06519                return null;
 6520            }
 6521
 06522            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 06523            var isIntelDx11OclSupported = isWindows
 06524                && _mediaEncoder.SupportsHwaccel("d3d11va")
 06525                && isQsvOclSupported;
 06526            var isIntelVaapiOclSupported = isLinux
 06527                && IsVaapiSupported(state)
 06528                && isQsvOclSupported;
 06529            var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
 06530                && _mediaEncoder.SupportsFilter("alphasrc");
 6531
 06532            var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06533                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06534            var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 06535            var is8_10_12bitSwFormatsQsv = is8_10bitSwFormatsQsv
 06536                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06537                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06538                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06539                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06540                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06541                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06542                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6543            // TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
 6544
 06545            if (is8bitSwFormatsQsv)
 6546            {
 06547                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06548                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6549                {
 06550                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6551                }
 6552
 06553                if (string.Equals(videoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase))
 6554                {
 06555                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6556                }
 6557
 06558                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6559                {
 06560                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6561                }
 6562
 06563                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6564                {
 06565                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6566                }
 6567            }
 6568
 06569            if (is8_10bitSwFormatsQsv)
 6570            {
 06571                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6572                {
 06573                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6574                }
 6575
 06576                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 6577                {
 06578                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6579                }
 6580            }
 6581
 06582            if (is8_10_12bitSwFormatsQsv)
 6583            {
 06584                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06585                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6586                {
 06587                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6588                }
 6589            }
 6590
 06591            return null;
 6592        }
 6593
 6594        public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6595        {
 06596            if ((!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
 06597                || options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 6598            {
 06599                return null;
 6600            }
 6601
 06602            var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
 06603            var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06604                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06605            var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06606            var is8_10_12bitSwFormatsNvdec = is8_10bitSwFormatsNvdec
 06607                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06608                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06609                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06610                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6611            // TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
 6612
 06613            if (is8bitSwFormatsNvdec)
 6614            {
 06615                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06616                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6617                {
 06618                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6619                }
 6620
 06621                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6622                {
 06623                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6624                }
 6625
 06626                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6627                {
 06628                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6629                }
 6630
 06631                if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6632                {
 06633                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpe
 6634                }
 6635
 06636                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6637                {
 06638                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6639                }
 6640            }
 6641
 06642            if (is8_10bitSwFormatsNvdec)
 6643            {
 06644                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6645                {
 06646                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6647                }
 6648
 06649                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6650                {
 06651                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6652                }
 6653            }
 6654
 06655            if (is8_10_12bitSwFormatsNvdec)
 6656            {
 06657                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06658                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6659                {
 06660                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6661                }
 6662            }
 6663
 06664            return null;
 6665        }
 6666
 6667        public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitD
 6668        {
 06669            if (!OperatingSystem.IsWindows()
 06670                || options.HardwareAccelerationType != HardwareAccelerationType.amf)
 6671            {
 06672                return null;
 6673            }
 6674
 06675            var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
 06676                && IsOpenclFullSupported()
 06677                && _mediaEncoder.SupportsFilter("alphasrc");
 06678            var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06679                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06680            var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 6681
 06682            if (is8bitSwFormatsAmf)
 6683            {
 06684                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06685                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6686                {
 06687                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6688                }
 6689
 06690                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6691                {
 06692                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6693                }
 6694
 06695                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6696                {
 06697                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6698                }
 6699            }
 6700
 06701            if (is8_10bitSwFormatsAmf)
 6702            {
 06703                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06704                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6705                {
 06706                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6707                }
 6708
 06709                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6710                {
 06711                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6712                }
 6713
 06714                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6715                {
 06716                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6717                }
 6718            }
 6719
 06720            return null;
 6721        }
 6722
 6723        public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6724        {
 06725            if (!OperatingSystem.IsLinux()
 06726                || options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 6727            {
 06728                return null;
 6729            }
 6730
 06731            var hwSurface = IsVaapiSupported(state)
 06732                && IsVaapiFullSupported()
 06733                && IsOpenclFullSupported()
 06734                && _mediaEncoder.SupportsFilter("alphasrc");
 06735            var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06736                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06737            var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06738            var is8_10_12bitSwFormatsVaapi = is8_10bitSwFormatsVaapi
 06739                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06740                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06741                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06742                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06743                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06744                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06745                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6746
 06747            if (is8bitSwFormatsVaapi)
 6748            {
 06749                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06750                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6751                {
 06752                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6753                }
 6754
 06755                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6756                {
 06757                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6758                }
 6759
 06760                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6761                {
 06762                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6763                }
 6764
 06765                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6766                {
 06767                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6768                }
 6769            }
 6770
 06771            if (is8_10bitSwFormatsVaapi)
 6772            {
 06773                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6774                {
 06775                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6776                }
 6777
 06778                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6779                {
 06780                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6781                }
 6782            }
 6783
 06784            if (is8_10_12bitSwFormatsVaapi)
 6785            {
 06786                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06787                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6788                {
 06789                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6790                }
 6791            }
 6792
 06793            return null;
 6794        }
 6795
 6796        public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream,
 6797        {
 06798            if (!OperatingSystem.IsMacOS()
 06799                || options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 6800            {
 06801                return null;
 6802            }
 6803
 06804            var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase
 06805                                    || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnore
 06806            var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, String
 06807            var is8_10_12bitSwFormatsVt = is8_10bitSwFormatsVt
 06808                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06809                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06810                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06811                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06812                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06813                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06814                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 06815            var isAv1SupportedSwFormatsVt = is8_10bitSwFormatsVt || string.Equals("yuv420p12le", videoStream.PixelFormat
 6816
 6817            // The related patches make videotoolbox hardware surface working is only available in jellyfin-ffmpeg 7.0.1
 06818            bool useHwSurface = (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface) && IsVideoToolboxFullSupp
 6819
 06820            if (is8bitSwFormatsVt)
 6821            {
 06822                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6823                {
 06824                    return GetHwaccelType(state, options, "vp8", bitDepth, useHwSurface);
 6825                }
 6826            }
 6827
 06828            if (is8_10bitSwFormatsVt)
 6829            {
 06830                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06831                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6832                {
 06833                    return GetHwaccelType(state, options, "h264", bitDepth, useHwSurface);
 6834                }
 6835
 06836                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6837                {
 06838                    return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
 6839                }
 6840            }
 6841
 06842            if (is8_10_12bitSwFormatsVt)
 6843            {
 06844                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06845                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6846                {
 06847                    return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
 6848                }
 6849
 06850                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06851                    && isAv1SupportedSwFormatsVt
 06852                    && _mediaEncoder.IsVideoToolboxAv1DecodeAvailable)
 6853                {
 06854                    return GetHwaccelType(state, options, "av1", bitDepth, useHwSurface);
 6855                }
 6856            }
 6857
 06858            return null;
 6859        }
 6860
 6861        public string GetRkmppVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6862        {
 06863            var isLinux = OperatingSystem.IsLinux();
 6864
 06865            if (!isLinux
 06866                || options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 6867            {
 06868                return null;
 6869            }
 6870
 06871            var inW = state.VideoStream?.Width;
 06872            var inH = state.VideoStream?.Height;
 06873            var reqW = state.BaseRequest.Width;
 06874            var reqH = state.BaseRequest.Height;
 06875            var reqMaxW = state.BaseRequest.MaxWidth;
 06876            var reqMaxH = state.BaseRequest.MaxHeight;
 6877
 6878            // rkrga RGA2e supports range from 1/16 to 16
 06879            if (!IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 16.0f))
 6880            {
 06881                return null;
 6882            }
 6883
 06884            var isRkmppOclSupported = IsRkmppFullSupported() && IsOpenclFullSupported();
 06885            var hwSurface = isRkmppOclSupported
 06886                && _mediaEncoder.SupportsFilter("alphasrc");
 6887
 6888            // rkrga RGA3 supports range from 1/8 to 8
 06889            var isAfbcSupported = hwSurface && IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 6890
 6891            // TODO: add more 8/10bit and 4:2:2 formats for Rkmpp after finishing the ffcheck tool
 06892            var is8bitSwFormatsRkmpp = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06893                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06894            var is10bitSwFormatsRkmpp = string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIg
 06895            var is8_10bitSwFormatsRkmpp = is8bitSwFormatsRkmpp || is10bitSwFormatsRkmpp;
 6896
 6897            // nv15 and nv20 are bit-stream only formats
 06898            if (is10bitSwFormatsRkmpp && !hwSurface)
 6899            {
 06900                return null;
 6901            }
 6902
 06903            if (is8bitSwFormatsRkmpp)
 6904            {
 06905                if (string.Equals(videoStream.Codec, "mpeg1video", StringComparison.OrdinalIgnoreCase))
 6906                {
 06907                    return GetHwaccelType(state, options, "mpeg1video", bitDepth, hwSurface);
 6908                }
 6909
 06910                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6911                {
 06912                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6913                }
 6914
 06915                if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 6916                {
 06917                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
 6918                }
 6919
 06920                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6921                {
 06922                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6923                }
 6924            }
 6925
 06926            if (is8_10bitSwFormatsRkmpp)
 6927            {
 06928                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06929                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6930                {
 06931                    var accelType = GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 06932                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 6933                }
 6934
 06935                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06936                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6937                {
 06938                    var accelType = GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 06939                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 6940                }
 6941
 06942                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6943                {
 06944                    var accelType = GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 06945                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 6946                }
 6947
 06948                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 6949                {
 06950                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6951                }
 6952            }
 6953
 06954            return null;
 6955        }
 6956
 6957        /// <summary>
 6958        /// Gets the number of threads.
 6959        /// </summary>
 6960        /// <param name="state">Encoding state.</param>
 6961        /// <param name="encodingOptions">Encoding options.</param>
 6962        /// <param name="outputVideoCodec">Video codec to use.</param>
 6963        /// <returns>Number of threads.</returns>
 6964#nullable enable
 6965        public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVide
 6966        {
 06967            var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount;
 6968
 06969            if (threads <= 0)
 6970            {
 6971                // Automatically set thread count
 06972                return 0;
 6973            }
 6974
 06975            return Math.Min(threads, Environment.ProcessorCount);
 6976        }
 6977
 6978#nullable disable
 6979        public void TryStreamCopy(EncodingJobInfo state)
 6980        {
 06981            if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream))
 6982            {
 06983                state.OutputVideoCodec = "copy";
 6984            }
 6985            else
 6986            {
 06987                var user = state.User;
 6988
 6989                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 06990                if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
 6991                {
 06992                    state.OutputVideoCodec = "copy";
 6993                }
 6994            }
 6995
 06996            if (state.AudioStream is not null
 06997                && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs))
 6998            {
 06999                state.OutputAudioCodec = "copy";
 7000            }
 7001            else
 7002            {
 07003                var user = state.User;
 7004
 7005                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07006                if (user is not null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
 7007                {
 07008                    state.OutputAudioCodec = "copy";
 7009                }
 7010            }
 07011        }
 7012
 7013        public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
 7014        {
 07015            var inputModifier = string.Empty;
 07016            var analyzeDurationArgument = string.Empty;
 7017
 7018            // Apply -analyzeduration as per the environment variable,
 7019            // otherwise ffmpeg will break on certain files due to default value is 0.
 07020            var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
 7021
 07022            if (state.MediaSource.AnalyzeDurationMs > 0)
 7023            {
 07024                analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToStr
 7025            }
 07026            else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
 7027            {
 07028                analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
 7029            }
 7030
 07031            if (!string.IsNullOrEmpty(analyzeDurationArgument))
 7032            {
 07033                inputModifier += " " + analyzeDurationArgument;
 7034            }
 7035
 07036            inputModifier = inputModifier.Trim();
 7037
 7038            // Apply -probesize if configured
 07039            var ffmpegProbeSize = _config.GetFFmpegProbeSize();
 7040
 07041            if (!string.IsNullOrEmpty(ffmpegProbeSize))
 7042            {
 07043                inputModifier += $" -probesize {ffmpegProbeSize}";
 7044            }
 7045
 07046            var userAgentParam = GetUserAgentParam(state);
 7047
 07048            if (!string.IsNullOrEmpty(userAgentParam))
 7049            {
 07050                inputModifier += " " + userAgentParam;
 7051            }
 7052
 07053            inputModifier = inputModifier.Trim();
 7054
 07055            var refererParam = GetRefererParam(state);
 7056
 07057            if (!string.IsNullOrEmpty(refererParam))
 7058            {
 07059                inputModifier += " " + refererParam;
 7060            }
 7061
 07062            inputModifier = inputModifier.Trim();
 7063
 07064            inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions, segmentContainer);
 07065            inputModifier = inputModifier.Trim();
 7066
 07067            if (state.InputProtocol == MediaProtocol.Rtsp)
 7068            {
 07069                inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
 7070            }
 7071
 07072            if (!string.IsNullOrEmpty(state.InputAudioSync))
 7073            {
 07074                inputModifier += " -async " + state.InputAudioSync;
 7075            }
 7076
 07077            if (!string.IsNullOrEmpty(state.InputVideoSync))
 7078            {
 07079                inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion);
 7080            }
 7081
 07082            if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp)
 7083            {
 07084                inputModifier += " -re";
 7085            }
 07086            else if (encodingOptions.EnableSegmentDeletion
 07087                && state.VideoStream is not null
 07088                && state.TranscodingType == TranscodingJobType.Hls
 07089                && IsCopyCodec(state.OutputVideoCodec)
 07090                && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateOption)
 7091            {
 7092                // Set an input read rate limit 10x for using SegmentDeletion with stream-copy
 7093                // to prevent ffmpeg from exiting prematurely (due to fast drive)
 07094                inputModifier += " -readrate 10";
 7095            }
 7096
 07097            var flags = new List<string>();
 07098            if (state.IgnoreInputDts)
 7099            {
 07100                flags.Add("+igndts");
 7101            }
 7102
 07103            if (state.IgnoreInputIndex)
 7104            {
 07105                flags.Add("+ignidx");
 7106            }
 7107
 07108            if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
 7109            {
 07110                flags.Add("+genpts");
 7111            }
 7112
 07113            if (state.DiscardCorruptFramesInput)
 7114            {
 07115                flags.Add("+discardcorrupt");
 7116            }
 7117
 07118            if (state.EnableFastSeekInput)
 7119            {
 07120                flags.Add("+fastseek");
 7121            }
 7122
 07123            if (flags.Count > 0)
 7124            {
 07125                inputModifier += " -fflags " + string.Join(string.Empty, flags);
 7126            }
 7127
 07128            if (state.IsVideoRequest)
 7129            {
 07130                if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && encodingOpt
 7131                {
 07132                    var inputFormat = GetInputFormat(state.InputContainer);
 07133                    if (!string.IsNullOrEmpty(inputFormat))
 7134                    {
 07135                        inputModifier += " -f " + inputFormat;
 7136                    }
 7137                }
 7138            }
 7139
 07140            if (state.MediaSource.RequiresLooping)
 7141            {
 07142                inputModifier += " -stream_loop -1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2";
 7143            }
 7144
 07145            return inputModifier;
 7146        }
 7147
 7148        public void AttachMediaSourceInfo(
 7149            EncodingJobInfo state,
 7150            EncodingOptions encodingOptions,
 7151            MediaSourceInfo mediaSource,
 7152            string requestedUrl)
 7153        {
 07154            ArgumentNullException.ThrowIfNull(state);
 7155
 07156            ArgumentNullException.ThrowIfNull(mediaSource);
 7157
 07158            var path = mediaSource.Path;
 07159            var protocol = mediaSource.Protocol;
 7160
 07161            if (!string.IsNullOrEmpty(mediaSource.EncoderPath) && mediaSource.EncoderProtocol.HasValue)
 7162            {
 07163                path = mediaSource.EncoderPath;
 07164                protocol = mediaSource.EncoderProtocol.Value;
 7165            }
 7166
 07167            state.MediaPath = path;
 07168            state.InputProtocol = protocol;
 07169            state.InputContainer = mediaSource.Container;
 07170            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07171            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 7172
 07173            state.IsoType = mediaSource.IsoType;
 7174
 07175            if (mediaSource.Timestamp.HasValue)
 7176            {
 07177                state.InputTimestamp = mediaSource.Timestamp.Value;
 7178            }
 7179
 07180            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07181            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 07182            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
 7183
 07184            if ((state.ReadInputAtNativeFramerate && !state.IsSegmentedLiveStream)
 07185                || (mediaSource.Protocol == MediaProtocol.File
 07186                && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)))
 7187            {
 07188                state.InputVideoSync = "-1";
 07189                state.InputAudioSync = "1";
 7190            }
 7191
 07192            if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase)
 07193                || string.Equals(mediaSource.Container, "asf", StringComparison.OrdinalIgnoreCase))
 7194            {
 7195                // Seeing some stuttering when transcoding wma to audio-only HLS
 07196                state.InputAudioSync = "1";
 7197            }
 7198
 07199            var mediaStreams = mediaSource.MediaStreams;
 7200
 07201            if (state.IsVideoRequest)
 7202            {
 07203                var videoRequest = state.BaseRequest;
 7204
 07205                if (string.IsNullOrEmpty(videoRequest.VideoCodec))
 7206                {
 07207                    if (string.IsNullOrEmpty(requestedUrl))
 7208                    {
 07209                        requestedUrl = "test." + videoRequest.Container;
 7210                    }
 7211
 07212                    videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
 7213                }
 7214
 07215                state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
 07216                state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Su
 07217                state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
 07218                state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
 7219
 07220                if (state.SubtitleStream is not null && !state.SubtitleStream.IsExternal)
 7221                {
 07222                    state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !
 7223                }
 7224
 07225                EnforceResolutionLimit(state);
 7226
 07227                NormalizeSubtitleEmbed(state);
 7228            }
 7229            else
 7230            {
 07231                state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
 7232            }
 7233
 07234            state.MediaSource = mediaSource;
 7235
 07236            var request = state.BaseRequest;
 07237            var supportedAudioCodecs = state.SupportedAudioCodecs;
 07238            if (request is not null && supportedAudioCodecs is not null && supportedAudioCodecs.Length > 0)
 7239            {
 07240                var supportedAudioCodecsList = supportedAudioCodecs.ToList();
 7241
 07242                ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
 7243
 07244                state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray();
 7245
 07246                request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(_mediaEncoder.CanEncodeToAudioCodec)
 07247                    ?? state.SupportedAudioCodecs.FirstOrDefault();
 7248            }
 7249
 07250            var supportedVideoCodecs = state.SupportedVideoCodecs;
 07251            if (request is not null && supportedVideoCodecs is not null && supportedVideoCodecs.Length > 0)
 7252            {
 07253                var supportedVideoCodecsList = supportedVideoCodecs.ToList();
 7254
 07255                ShiftVideoCodecsIfNeeded(supportedVideoCodecsList, encodingOptions);
 7256
 07257                state.SupportedVideoCodecs = supportedVideoCodecsList.ToArray();
 7258
 07259                request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
 7260            }
 07261        }
 7262
 7263        private void ShiftAudioCodecsIfNeeded(List<string> audioCodecs, MediaStream audioStream)
 7264        {
 7265            // No need to shift if there is only one supported audio codec.
 07266            if (audioCodecs.Count < 2)
 7267            {
 07268                return;
 7269            }
 7270
 07271            var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
 07272            var shiftAudioCodecs = new List<string>();
 07273            if (inputChannels >= 6)
 7274            {
 7275                // DTS and TrueHD are not supported by HLS
 7276                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07277                shiftAudioCodecs.Add("dts");
 07278                shiftAudioCodecs.Add("truehd");
 7279            }
 7280            else
 7281            {
 7282                // Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
 7283                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07284                shiftAudioCodecs.Add("ac3");
 07285                shiftAudioCodecs.Add("eac3");
 7286            }
 7287
 07288            if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7289            {
 07290                return;
 7291            }
 7292
 07293            while (shiftAudioCodecs.Contains(audioCodecs[0], StringComparison.OrdinalIgnoreCase))
 7294            {
 07295                var removed = audioCodecs[0];
 07296                audioCodecs.RemoveAt(0);
 07297                audioCodecs.Add(removed);
 7298            }
 07299        }
 7300
 7301        private void ShiftVideoCodecsIfNeeded(List<string> videoCodecs, EncodingOptions encodingOptions)
 7302        {
 7303            // No need to shift if there is only one supported video codec.
 07304            if (videoCodecs.Count < 2)
 7305            {
 07306                return;
 7307            }
 7308
 7309            // Shift codecs to the end of list if it's not allowed.
 07310            var shiftVideoCodecs = new List<string>();
 07311            if (!encodingOptions.AllowHevcEncoding)
 7312            {
 07313                shiftVideoCodecs.Add("hevc");
 07314                shiftVideoCodecs.Add("h265");
 7315            }
 7316
 07317            if (!encodingOptions.AllowAv1Encoding)
 7318            {
 07319                shiftVideoCodecs.Add("av1");
 7320            }
 7321
 07322            if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7323            {
 07324                return;
 7325            }
 7326
 07327            while (shiftVideoCodecs.Contains(videoCodecs[0], StringComparison.OrdinalIgnoreCase))
 7328            {
 07329                var removed = videoCodecs[0];
 07330                videoCodecs.RemoveAt(0);
 07331                videoCodecs.Add(removed);
 7332            }
 07333        }
 7334
 7335        private void NormalizeSubtitleEmbed(EncodingJobInfo state)
 7336        {
 07337            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7338            {
 07339                return;
 7340            }
 7341
 7342            // This is tricky to remux in, after converting to dvdsub it's not positioned correctly
 7343            // Therefore, let's just burn it in
 07344            if (string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 7345            {
 07346                state.SubtitleDeliveryMethod = SubtitleDeliveryMethod.Encode;
 7347            }
 07348        }
 7349
 7350        public string GetSubtitleEmbedArguments(EncodingJobInfo state)
 7351        {
 07352            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7353            {
 07354                return string.Empty;
 7355            }
 7356
 07357            var format = state.SupportedSubtitleCodecs.FirstOrDefault();
 7358            string codec;
 7359
 07360            if (string.IsNullOrEmpty(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.Ordin
 7361            {
 07362                codec = "copy";
 7363            }
 7364            else
 7365            {
 07366                codec = format;
 7367            }
 7368
 07369            return " -codec:s:0 " + codec + " -disposition:s:0 default";
 7370        }
 7371
 7372        public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, Encoder
 7373        {
 7374            // Get the output codec name
 07375            var videoCodec = GetVideoEncoder(state, encodingOptions);
 7376
 07377            var format = string.Empty;
 07378            var keyFrame = string.Empty;
 07379            var outputPath = state.OutputFilePath;
 7380
 07381            if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase)
 07382                && state.BaseRequest.Context == EncodingContext.Streaming)
 7383            {
 7384                // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
 07385                format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov";
 7386            }
 7387
 07388            var threads = GetNumberOfThreads(state, encodingOptions, videoCodec);
 7389
 07390            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7391
 07392            return string.Format(
 07393                CultureInfo.InvariantCulture,
 07394                "{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
 07395                inputModifier,
 07396                GetInputArgument(state, encodingOptions, null),
 07397                keyFrame,
 07398                GetMapArgs(state),
 07399                GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset),
 07400                threads,
 07401                GetProgressiveVideoAudioArguments(state, encodingOptions),
 07402                GetSubtitleEmbedArguments(state),
 07403                format,
 07404                outputPath).Trim();
 7405        }
 7406
 7407        public string GetOutputFFlags(EncodingJobInfo state)
 7408        {
 07409            var flags = new List<string>();
 07410            if (state.GenPtsOutput)
 7411            {
 07412                flags.Add("+genpts");
 7413            }
 7414
 07415            if (flags.Count > 0)
 7416            {
 07417                return " -fflags " + string.Join(string.Empty, flags);
 7418            }
 7419
 07420            return string.Empty;
 7421        }
 7422
 7423        public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoC
 7424        {
 07425            var args = "-codec:v:0 " + videoCodec;
 7426
 07427            if (state.BaseRequest.EnableMpegtsM2TsMode)
 7428            {
 07429                args += " -mpegts_m2ts_mode 1";
 7430            }
 7431
 07432            if (IsCopyCodec(videoCodec))
 7433            {
 07434                if (state.VideoStream is not null
 07435                    && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
 07436                    && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
 7437                {
 07438                    string bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Video);
 07439                    if (!string.IsNullOrEmpty(bitStreamArgs))
 7440                    {
 07441                        args += " " + bitStreamArgs;
 7442                    }
 7443                }
 7444
 07445                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7446                {
 07447                    args += " -copyts -avoid_negative_ts disabled -start_at_zero";
 7448                }
 7449
 07450                if (!state.RunTimeTicks.HasValue)
 7451                {
 07452                    args += " -fflags +genpts";
 7453                }
 7454            }
 7455            else
 7456            {
 07457                var keyFrameArg = string.Format(
 07458                    CultureInfo.InvariantCulture,
 07459                    " -force_key_frames \"expr:gte(t,n_forced*{0})\"",
 07460                    5);
 7461
 07462                args += keyFrameArg;
 7463
 07464                var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream &&
 7465
 07466                var hasCopyTs = false;
 7467
 7468                // video processing filters.
 07469                var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
 7470
 07471                var negativeMapArgs = GetNegativeMapArgsByFilters(state, videoProcessParam);
 7472
 07473                args = negativeMapArgs + args + videoProcessParam;
 7474
 07475                hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
 7476
 07477                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7478                {
 07479                    if (!hasCopyTs)
 7480                    {
 07481                        args += " -copyts";
 7482                    }
 7483
 07484                    args += " -avoid_negative_ts disabled";
 7485
 07486                    if (!(state.SubtitleStream is not null && state.SubtitleStream.IsExternal && !state.SubtitleStream.I
 7487                    {
 07488                        args += " -start_at_zero";
 7489                    }
 7490                }
 7491
 07492                var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset);
 7493
 07494                if (!string.IsNullOrEmpty(qualityParam))
 7495                {
 07496                    args += " " + qualityParam.Trim();
 7497                }
 7498            }
 7499
 07500            if (!string.IsNullOrEmpty(state.OutputVideoSync))
 7501            {
 07502                args += GetVideoSyncOption(state.OutputVideoSync, _mediaEncoder.EncoderVersion);
 7503            }
 7504
 07505            args += GetOutputFFlags(state);
 7506
 07507            return args;
 7508        }
 7509
 7510        public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions)
 7511        {
 7512            // If the video doesn't have an audio stream, return a default.
 07513            if (state.AudioStream is null && state.VideoStream is not null)
 7514            {
 07515                return string.Empty;
 7516            }
 7517
 7518            // Get the output codec name
 07519            var codec = GetAudioEncoder(state);
 7520
 07521            var args = "-codec:a:0 " + codec;
 7522
 07523            if (IsCopyCodec(codec))
 7524            {
 07525                return args;
 7526            }
 7527
 07528            var channels = state.OutputAudioChannels;
 7529
 07530            var useDownMixAlgorithm = state.AudioStream is not null
 07531                                      && DownMixAlgorithmsHelper.AlgorithmFilterStrings.ContainsKey((encodingOptions.Dow
 7532
 07533            if (channels.HasValue && !useDownMixAlgorithm)
 7534            {
 07535                args += " -ac " + channels.Value;
 7536            }
 7537
 07538            var bitrate = state.OutputAudioBitrate;
 07539            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
 7540            {
 07541                var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2);
 07542                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7543                {
 07544                    args += vbrParam;
 7545                }
 7546                else
 7547                {
 07548                    args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
 7549                }
 7550            }
 7551
 07552            if (state.OutputAudioSampleRate.HasValue)
 7553            {
 07554                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
 7555            }
 7556
 07557            args += GetAudioFilterParam(state, encodingOptions);
 7558
 07559            return args;
 7560        }
 7561
 7562        public string GetProgressiveAudioFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string 
 7563        {
 07564            var audioTranscodeParams = new List<string>();
 7565
 07566            var bitrate = state.OutputAudioBitrate;
 07567            var channels = state.OutputAudioChannels;
 07568            var outputCodec = state.OutputAudioCodec;
 7569
 07570            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
 7571            {
 07572                var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2);
 07573                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7574                {
 07575                    audioTranscodeParams.Add(vbrParam);
 7576                }
 7577                else
 7578                {
 07579                    audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
 7580                }
 7581            }
 7582
 07583            if (channels.HasValue)
 7584            {
 07585                audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)
 7586            }
 7587
 07588            if (!string.IsNullOrEmpty(outputCodec))
 7589            {
 07590                audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
 7591            }
 7592
 07593            if (GetAudioEncoder(state).StartsWith("pcm_", StringComparison.Ordinal))
 7594            {
 07595                audioTranscodeParams.Add(string.Concat("-f ", GetAudioEncoder(state).AsSpan(4)));
 07596                audioTranscodeParams.Add("-ar " + state.BaseRequest.AudioBitRate);
 7597            }
 7598
 07599            if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
 7600            {
 7601                // opus only supports specific sampling rates
 07602                var sampleRate = state.OutputAudioSampleRate;
 07603                if (sampleRate.HasValue)
 7604                {
 07605                    var sampleRateValue = sampleRate.Value switch
 07606                    {
 07607                        <= 8000 => 8000,
 07608                        <= 12000 => 12000,
 07609                        <= 16000 => 16000,
 07610                        <= 24000 => 24000,
 07611                        _ => 48000
 07612                    };
 7613
 07614                    audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
 7615                }
 7616            }
 7617
 7618            // Copy the movflags from GetProgressiveVideoFullCommandLine
 7619            // See #9248 and the associated PR for why this is needed
 07620            if (_mp4ContainerNames.Contains(state.OutputContainer))
 7621            {
 07622                audioTranscodeParams.Add("-movflags empty_moov+delay_moov");
 7623            }
 7624
 07625            var threads = GetNumberOfThreads(state, encodingOptions, null);
 7626
 07627            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7628
 07629            return string.Format(
 07630                CultureInfo.InvariantCulture,
 07631                "{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"",
 07632                inputModifier,
 07633                GetInputArgument(state, encodingOptions, null),
 07634                threads,
 07635                " -vn",
 07636                string.Join(' ', audioTranscodeParams),
 07637                outputPath,
 07638                string.Empty,
 07639                string.Empty,
 07640                string.Empty).Trim();
 7641        }
 7642
 7643        public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
 7644        {
 07645            var index = 0;
 07646            var length = mediaStreams.Count;
 7647
 07648            for (var i = 0; i < length; i++)
 7649            {
 07650                var currentMediaStream = mediaStreams[i];
 07651                if (currentMediaStream == streamToFind)
 7652                {
 07653                    return index;
 7654                }
 7655
 07656                if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
 7657                {
 07658                    index++;
 7659                }
 7660            }
 7661
 07662            return -1;
 7663        }
 7664
 7665        public static bool IsCopyCodec(string codec)
 7666        {
 07667            return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
 7668        }
 7669
 7670        private static bool ShouldEncodeSubtitle(EncodingJobInfo state)
 7671        {
 07672            return state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
 07673                   || (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec));
 7674        }
 7675
 7676        public static string GetVideoSyncOption(string videoSync, Version encoderVersion)
 7677        {
 07678            if (string.IsNullOrEmpty(videoSync))
 7679            {
 07680                return string.Empty;
 7681            }
 7682
 07683            if (encoderVersion >= new Version(5, 1))
 7684            {
 07685                if (int.TryParse(videoSync, CultureInfo.InvariantCulture, out var vsync))
 7686                {
 07687                    return vsync switch
 07688                    {
 07689                        -1 => " -fps_mode auto",
 07690                        0 => " -fps_mode passthrough",
 07691                        1 => " -fps_mode cfr",
 07692                        2 => " -fps_mode vfr",
 07693                        _ => string.Empty
 07694                    };
 7695                }
 7696
 07697                return string.Empty;
 7698            }
 7699
 7700            // -vsync is deprecated in FFmpeg 5.1 and will be removed in the future.
 07701            return $" -vsync {videoSync}";
 7702        }
 7703    }
 7704}

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)
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)