< 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: 21
Uncovered lines: 3432
Coverable lines: 3453
Total lines: 7261
Line coverage: 0.6%
Branch coverage
0%
Covered branches: 0
Total branches: 3300
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%110100%
IsVaapiSupported(...)0%2040%
IsVaapiFullSupported()0%272160%
IsRkmppFullSupported()0%4260%
IsOpenclFullSupported()0%4260%
IsCudaFullSupported()0%110100%
IsVulkanFullSupported()0%110100%
IsVideoToolboxFullSupported()0%7280%
IsSwTonemapAvailable(...)0%7280%
IsHwTonemapAvailable(...)0%420200%
IsVulkanHwTonemapAvailable(...)0%4260%
IsIntelVppTonemapAvailable(...)0%272160%
IsVideoToolboxTonemapAvailable(...)0%272160%
GetVideoEncoder(...)0%210140%
GetUserAgentParam(...)0%620%
GetRefererParam(...)0%620%
GetInputFormat(...)0%1482380%
GetDecoderFromCodec(...)0%7280%
InferAudioCodec(...)0%342180%
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%156120%
GetDrmDeviceArgs(...)0%2040%
GetQsvDeviceArgs(...)0%7280%
GetFilterHwDeviceArgs(...)0%620%
GetGraphicalSubCanvasSize(...)0%420200%
GetInputVideoHwaccelArgs(...)0%145201200%
GetInputArgument(...)0%1190340%
IsH264(...)0%2040%
IsH265(...)0%2040%
IsAAC(...)0%620%
GetBitStreamArgs(...)0%4260%
GetAudioBitStreamArguments(...)0%110100%
GetSegmentFileExtension(...)0%620%
GetVideoBitrateParam(...)0%812280%
GetEncoderParam(...)0%7656870%
NormalizeTranscodingLevel(...)0%506220%
GetTextSubtitlesFilter(...)0%210140%
GetFramerateParam(...)0%156120%
GetHlsVideoKeyFrameArguments(...)0%1806420%
GetVideoQualityParam(...)0%318621780%
CanStreamCopyVideo(...)0%126561120%
CanStreamCopyAudio(...)0%1190340%
GetVideoBitrateParamValue(...)0%506220%
GetMinBitrate(...)0%2040%
GetVideoBitrateScaleFactor(...)0%7280%
ScaleBitrate(...)0%7280%
GetAudioBitrateParam(...)100%210%
GetAudioBitrateParam(...)0%2162460%
GetAudioVbrModeParam(...)0%1190340%
GetAudioFilterParam(...)0%702260%
GetNumAudioChannelsParam(...)0%930300%
EnforceResolutionLimit(...)0%2040%
GetFastSeekCommandLineParameter(...)0%420200%
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%1190340%
GetLibplaceboFilter(...)0%420200%
GetVideoTransposeDirection(...)0%156120%
GetSwVidFilterChain(...)0%2550500%
GetNvidiaVidFilterChain(...)0%110100%
GetNvidiaVidFiltersPrefered(...)0%7140840%
GetAmdVidFilterChain(...)0%210140%
GetAmdDx11VidFiltersPrefered(...)0%7140840%
GetIntelVidFilterChain(...)0%506220%
GetIntelQsvDx11VidFiltersPrefered(...)0%150061220%
GetIntelQsvVaapiVidFiltersPrefered(...)0%126561120%
GetVaapiVidFilterChain(...)0%1190340%
GetIntelVaapiFullVidFiltersPrefered(...)0%101001000%
GetAmdVaapiFullVidFiltersPrefered(...)0%9312960%
GetVaapiLimitedVidFiltersPrefered(...)0%6480800%
GetAppleVidFilterChain(...)0%156120%
GetAppleVidFiltersPreferred(...)0%5550740%
GetRkmppVidFilterChain(...)0%272160%
GetRkmppVidFiltersPrefered(...)0%105061020%
GetVideoProcessingFilterParam(...)0%2756520%
GetOverwriteColorPropertiesParam(...)0%2040%
GetInputHdrParam(...)0%620%
GetOutputSdrParam(...)0%2040%
GetVideoColorBitDepth(...)0%600240%
GetHardwareVideoDecoder(...)0%2756520%
GetHwDecoderName(...)0%1056320%
GetHwaccelType(...)0%105061020%
GetQsvHwVidDecoder(...)0%1640400%
GetNvdecVidDecoder(...)0%1332360%
GetAmfVidDecoder(...)0%1056320%
GetVaapiVidDecoder(...)0%1332360%
GetVideotoolboxVidDecoder(...)0%702260%
GetRkmppVidDecoder(...)0%2756520%
GetNumberOfThreads(...)0%4260%
TryStreamCopy(...)0%272160%
GetInputModifier(...)0%3660600%
AttachMediaSourceInfo(...)0%1640400%
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%

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.Enums;
 17using Jellyfin.Extensions;
 18using MediaBrowser.Common.Configuration;
 19using MediaBrowser.Controller.Extensions;
 20using MediaBrowser.Model.Configuration;
 21using MediaBrowser.Model.Dlna;
 22using MediaBrowser.Model.Dto;
 23using MediaBrowser.Model.Entities;
 24using MediaBrowser.Model.MediaInfo;
 25using Microsoft.Extensions.Configuration;
 26using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
 27
 28namespace MediaBrowser.Controller.MediaEncoding
 29{
 30    public partial class EncodingHelper
 31    {
 32        /// <summary>
 33        /// The codec validation regex.
 34        /// This regular expression matches strings that consist of alphanumeric characters, hyphens,
 35        /// periods, underscores, commas, and vertical bars, with a length between 0 and 40 characters.
 36        /// This should matches all common valid codecs.
 37        /// </summary>
 38        public const string ValidationRegex = @"^[a-zA-Z0-9\-\._,|]{0,40}$";
 39
 40        private const string _defaultMjpegEncoder = "mjpeg";
 41
 42        private const string QsvAlias = "qs";
 43        private const string VaapiAlias = "va";
 44        private const string D3d11vaAlias = "dx11";
 45        private const string VideotoolboxAlias = "vt";
 46        private const string RkmppAlias = "rk";
 47        private const string OpenclAlias = "ocl";
 48        private const string CudaAlias = "cu";
 49        private const string DrmAlias = "dr";
 50        private const string VulkanAlias = "vk";
 51        private readonly IApplicationPaths _appPaths;
 52        private readonly IMediaEncoder _mediaEncoder;
 53        private readonly ISubtitleEncoder _subtitleEncoder;
 54        private readonly IConfiguration _config;
 55        private readonly IConfigurationManager _configurationManager;
 56
 57        // i915 hang was fixed by linux 6.2 (3f882f2)
 2258        private readonly Version _minKerneli915Hang = new Version(5, 18);
 2259        private readonly Version _maxKerneli915Hang = new Version(6, 1, 3);
 2260        private readonly Version _minFixedKernel60i915Hang = new Version(6, 0, 18);
 2261        private readonly Version _minKernelVersionAmdVkFmtModifier = new Version(5, 15);
 62
 2263        private readonly Version _minFFmpegImplictHwaccel = new Version(6, 0);
 2264        private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0);
 2265        private readonly Version _minFFmpegOclCuTonemapMode = new Version(5, 1, 3);
 2266        private readonly Version _minFFmpegSvtAv1Params = new Version(5, 1);
 2267        private readonly Version _minFFmpegVaapiH26xEncA53CcSei = new Version(6, 0);
 2268        private readonly Version _minFFmpegReadrateOption = new Version(5, 0);
 2269        private readonly Version _minFFmpegWorkingVtHwSurface = new Version(7, 0, 1);
 2270        private readonly Version _minFFmpegDisplayRotationOption = new Version(6, 0);
 2271        private readonly Version _minFFmpegAdvancedTonemapMode = new Version(7, 0, 1);
 2272        private readonly Version _minFFmpegAlteredVaVkInterop = new Version(7, 0, 1);
 2273        private readonly Version _minFFmpegQsvVppTonemapOption = new Version(7, 0, 1);
 74
 075        private static readonly Regex _validationRegex = new(ValidationRegex, RegexOptions.Compiled);
 76
 077        private static readonly string[] _videoProfilesH264 =
 078        [
 079            "ConstrainedBaseline",
 080            "Baseline",
 081            "Extended",
 082            "Main",
 083            "High",
 084            "ProgressiveHigh",
 085            "ConstrainedHigh",
 086            "High10"
 087        ];
 88
 089        private static readonly string[] _videoProfilesH265 =
 090        [
 091            "Main",
 092            "Main10"
 093        ];
 94
 095        private static readonly string[] _videoProfilesAv1 =
 096        [
 097            "Main",
 098            "High",
 099            "Professional",
 0100        ];
 101
 0102        private static readonly HashSet<string> _mp4ContainerNames = new(StringComparer.OrdinalIgnoreCase)
 0103        {
 0104            "mp4",
 0105            "m4a",
 0106            "m4p",
 0107            "m4b",
 0108            "m4r",
 0109            "m4v",
 0110        };
 111
 0112        private static readonly TonemappingMode[] _legacyTonemapModes = [TonemappingMode.max, TonemappingMode.rgb];
 0113        private static readonly TonemappingMode[] _advancedTonemapModes = [TonemappingMode.lum, TonemappingMode.itp];
 114
 115        // Set max transcoding channels for encoders that can't handle more than a set amount of channels
 116        // AAC, FLAC, ALAC, libopus, libvorbis encoders all support at least 8 channels
 0117        private static readonly Dictionary<string, int> _audioTranscodeChannelLookup = new(StringComparer.OrdinalIgnoreC
 0118        {
 0119            { "libmp3lame", 2 },
 0120            { "libfdk_aac", 6 },
 0121            { "ac3", 6 },
 0122            { "eac3", 6 },
 0123            { "dca", 6 },
 0124            { "mlp", 6 },
 0125            { "truehd", 6 },
 0126        };
 127
 0128        private static readonly Dictionary<HardwareAccelerationType, string> _mjpegCodecMap = new()
 0129        {
 0130            { HardwareAccelerationType.vaapi, _defaultMjpegEncoder + "_vaapi" },
 0131            { HardwareAccelerationType.qsv, _defaultMjpegEncoder + "_qsv" },
 0132            { HardwareAccelerationType.videotoolbox, _defaultMjpegEncoder + "_videotoolbox" },
 0133            { HardwareAccelerationType.rkmpp, _defaultMjpegEncoder + "_rkmpp" }
 0134        };
 135
 0136        public static readonly string[] LosslessAudioCodecs =
 0137        [
 0138            "alac",
 0139            "ape",
 0140            "flac",
 0141            "mlp",
 0142            "truehd",
 0143            "wavpack"
 0144        ];
 145
 146        public EncodingHelper(
 147            IApplicationPaths appPaths,
 148            IMediaEncoder mediaEncoder,
 149            ISubtitleEncoder subtitleEncoder,
 150            IConfiguration config,
 151            IConfigurationManager configurationManager)
 152        {
 22153            _appPaths = appPaths;
 22154            _mediaEncoder = mediaEncoder;
 22155            _subtitleEncoder = subtitleEncoder;
 22156            _config = config;
 22157            _configurationManager = configurationManager;
 22158        }
 159
 160        [GeneratedRegex(@"\s+")]
 161        private static partial Regex WhiteSpaceRegex();
 162
 163        public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0164            => GetH26xOrAv1Encoder("libx264", "h264", state, encodingOptions);
 165
 166        public string GetH265Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0167            => GetH26xOrAv1Encoder("libx265", "hevc", state, encodingOptions);
 168
 169        public string GetAv1Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0170            => GetH26xOrAv1Encoder("libsvtav1", "av1", state, encodingOptions);
 171
 172        private string GetH26xOrAv1Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptio
 173        {
 174            // Only use alternative encoders for video files.
 175            // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying
 176            // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such 
 0177            if (state.VideoType == VideoType.VideoFile)
 178            {
 0179                var hwType = encodingOptions.HardwareAccelerationType;
 180
 0181                var codecMap = new Dictionary<HardwareAccelerationType, string>()
 0182                {
 0183                    { HardwareAccelerationType.amf,                  hwEncoder + "_amf" },
 0184                    { HardwareAccelerationType.nvenc,                hwEncoder + "_nvenc" },
 0185                    { HardwareAccelerationType.qsv,                  hwEncoder + "_qsv" },
 0186                    { HardwareAccelerationType.vaapi,                hwEncoder + "_vaapi" },
 0187                    { HardwareAccelerationType.videotoolbox,         hwEncoder + "_videotoolbox" },
 0188                    { HardwareAccelerationType.v4l2m2m,              hwEncoder + "_v4l2m2m" },
 0189                    { HardwareAccelerationType.rkmpp,                hwEncoder + "_rkmpp" },
 0190                };
 191
 0192                if (hwType != HardwareAccelerationType.none
 0193                    && encodingOptions.EnableHardwareEncoding
 0194                    && codecMap.TryGetValue(hwType, out var preferredEncoder)
 0195                    && _mediaEncoder.SupportsEncoder(preferredEncoder))
 196                {
 0197                    return preferredEncoder;
 198                }
 199            }
 200
 0201            return defaultEncoder;
 202        }
 203
 204        private string GetMjpegEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 205        {
 0206            if (state.VideoType == VideoType.VideoFile)
 207            {
 0208                var hwType = encodingOptions.HardwareAccelerationType;
 209
 0210                if (hwType != HardwareAccelerationType.none
 0211                    && encodingOptions.EnableHardwareEncoding
 0212                    && _mjpegCodecMap.TryGetValue(hwType, out var preferredEncoder)
 0213                    && _mediaEncoder.SupportsEncoder(preferredEncoder))
 214                {
 0215                    return preferredEncoder;
 216                }
 217            }
 218
 0219            return _defaultMjpegEncoder;
 220        }
 221
 222        private bool IsVaapiSupported(EncodingJobInfo state)
 223        {
 224            // vaapi will throw an error with this input
 225            // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99.
 0226            if (string.Equals(state.VideoStream?.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 227            {
 0228                return false;
 229            }
 230
 0231            return _mediaEncoder.SupportsHwaccel("vaapi");
 232        }
 233
 234        private bool IsVaapiFullSupported()
 235        {
 0236            return _mediaEncoder.SupportsHwaccel("drm")
 0237                   && _mediaEncoder.SupportsHwaccel("vaapi")
 0238                   && _mediaEncoder.SupportsFilter("scale_vaapi")
 0239                   && _mediaEncoder.SupportsFilter("deinterlace_vaapi")
 0240                   && _mediaEncoder.SupportsFilter("tonemap_vaapi")
 0241                   && _mediaEncoder.SupportsFilter("procamp_vaapi")
 0242                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVaapiFrameSync)
 0243                   && _mediaEncoder.SupportsFilter("transpose_vaapi")
 0244                   && _mediaEncoder.SupportsFilter("hwupload_vaapi");
 245        }
 246
 247        private bool IsRkmppFullSupported()
 248        {
 0249            return _mediaEncoder.SupportsHwaccel("rkmpp")
 0250                   && _mediaEncoder.SupportsFilter("scale_rkrga")
 0251                   && _mediaEncoder.SupportsFilter("vpp_rkrga")
 0252                   && _mediaEncoder.SupportsFilter("overlay_rkrga");
 253        }
 254
 255        private bool IsOpenclFullSupported()
 256        {
 0257            return _mediaEncoder.SupportsHwaccel("opencl")
 0258                   && _mediaEncoder.SupportsFilter("scale_opencl")
 0259                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390)
 0260                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclFrameSync);
 261
 262            // Let transpose_opencl optional for the time being.
 263        }
 264
 265        private bool IsCudaFullSupported()
 266        {
 0267            return _mediaEncoder.SupportsHwaccel("cuda")
 0268                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat)
 0269                   && _mediaEncoder.SupportsFilter("yadif_cuda")
 0270                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName)
 0271                   && _mediaEncoder.SupportsFilter("overlay_cuda")
 0272                   && _mediaEncoder.SupportsFilter("hwupload_cuda");
 273
 274            // Let transpose_cuda optional for the time being.
 275        }
 276
 277        private bool IsVulkanFullSupported()
 278        {
 0279            return _mediaEncoder.SupportsHwaccel("vulkan")
 0280                   && _mediaEncoder.SupportsFilter("libplacebo")
 0281                   && _mediaEncoder.SupportsFilter("scale_vulkan")
 0282                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVulkanFrameSync)
 0283                   && _mediaEncoder.SupportsFilter("transpose_vulkan")
 0284                   && _mediaEncoder.SupportsFilter("flip_vulkan");
 285        }
 286
 287        private bool IsVideoToolboxFullSupported()
 288        {
 0289            return _mediaEncoder.SupportsHwaccel("videotoolbox")
 0290                && _mediaEncoder.SupportsFilter("yadif_videotoolbox")
 0291                && _mediaEncoder.SupportsFilter("overlay_videotoolbox")
 0292                && _mediaEncoder.SupportsFilter("tonemap_videotoolbox")
 0293                && _mediaEncoder.SupportsFilter("scale_vt");
 294
 295            // Let transpose_vt optional for the time being.
 296        }
 297
 298        private bool IsSwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 299        {
 0300            if (state.VideoStream is null
 0301                || !options.EnableTonemapping
 0302                || GetVideoColorBitDepth(state) != 10
 0303                || !_mediaEncoder.SupportsFilter("tonemapx"))
 304            {
 0305                return false;
 306            }
 307
 0308            return state.VideoStream.VideoRange == VideoRange.HDR;
 309        }
 310
 311        private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 312        {
 0313            if (state.VideoStream is null
 0314                || !options.EnableTonemapping
 0315                || GetVideoColorBitDepth(state) != 10)
 316            {
 0317                return false;
 318            }
 319
 0320            if (state.VideoStream.VideoRange == VideoRange.HDR
 0321                && state.VideoStream.VideoRangeType == VideoRangeType.DOVI)
 322            {
 323                // Only native SW decoder and HW accelerator can parse dovi rpu.
 0324                var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 0325                var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 0326                var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 0327                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 0328                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 0329                var isVideoToolBoxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 0330                return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder || isVideoToolBoxDecoder;
 331            }
 332
 0333            return state.VideoStream.VideoRange == VideoRange.HDR
 0334                   && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
 0335                       || state.VideoStream.VideoRangeType == VideoRangeType.HLG
 0336                       || state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10
 0337                       || state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHLG);
 338        }
 339
 340        private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 341        {
 0342            if (state.VideoStream is null)
 343            {
 0344                return false;
 345            }
 346
 347            // libplacebo has partial Dolby Vision to SDR tonemapping support.
 0348            return options.EnableTonemapping
 0349                   && state.VideoStream.VideoRange == VideoRange.HDR
 0350                   && GetVideoColorBitDepth(state) == 10;
 351        }
 352
 353        private bool IsIntelVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 354        {
 0355            if (state.VideoStream is null
 0356                || !options.EnableVppTonemapping
 0357                || GetVideoColorBitDepth(state) != 10)
 358            {
 0359                return false;
 360            }
 361
 362            // prefer 'tonemap_vaapi' over 'vpp_qsv' on Linux for supporting Gen9/KBLx.
 363            // 'vpp_qsv' requires VPL, which is only supported on Gen12/TGLx and newer.
 0364            if (OperatingSystem.IsWindows()
 0365                && options.HardwareAccelerationType == HardwareAccelerationType.qsv
 0366                && _mediaEncoder.EncoderVersion < _minFFmpegQsvVppTonemapOption)
 367            {
 0368                return false;
 369            }
 370
 0371            return state.VideoStream.VideoRange == VideoRange.HDR
 0372                   && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
 0373                       || state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10);
 374        }
 375
 376        private bool IsVideoToolboxTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 377        {
 0378            if (state.VideoStream is null
 0379                || !options.EnableVideoToolboxTonemapping
 0380                || GetVideoColorBitDepth(state) != 10)
 381            {
 0382                return false;
 383            }
 384
 385            // Certain DV profile 5 video works in Safari with direct playing, but the VideoToolBox does not produce cor
 386            // All other HDR formats working.
 0387            return state.VideoStream.VideoRange == VideoRange.HDR
 0388                   && state.VideoStream.VideoRangeType is VideoRangeType.HDR10 or VideoRangeType.HLG or VideoRangeType.H
 389        }
 390
 391        /// <summary>
 392        /// Gets the name of the output video codec.
 393        /// </summary>
 394        /// <param name="state">Encoding state.</param>
 395        /// <param name="encodingOptions">Encoding options.</param>
 396        /// <returns>Encoder string.</returns>
 397        public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 398        {
 0399            var codec = state.OutputVideoCodec;
 400
 0401            if (!string.IsNullOrEmpty(codec))
 402            {
 0403                if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 404                {
 0405                    return GetAv1Encoder(state, encodingOptions);
 406                }
 407
 0408                if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 0409                    || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
 410                {
 0411                    return GetH265Encoder(state, encodingOptions);
 412                }
 413
 0414                if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
 415                {
 0416                    return GetH264Encoder(state, encodingOptions);
 417                }
 418
 0419                if (string.Equals(codec, "mjpeg", StringComparison.OrdinalIgnoreCase))
 420                {
 0421                    return GetMjpegEncoder(state, encodingOptions);
 422                }
 423
 0424                if (_validationRegex.IsMatch(codec))
 425                {
 0426                    return codec.ToLowerInvariant();
 427                }
 428            }
 429
 0430            return "copy";
 431        }
 432
 433        /// <summary>
 434        /// Gets the user agent param.
 435        /// </summary>
 436        /// <param name="state">The state.</param>
 437        /// <returns>System.String.</returns>
 438        public string GetUserAgentParam(EncodingJobInfo state)
 439        {
 0440            if (state.RemoteHttpHeaders.TryGetValue("User-Agent", out string useragent))
 441            {
 0442                return "-user_agent \"" + useragent + "\"";
 443            }
 444
 0445            return string.Empty;
 446        }
 447
 448        /// <summary>
 449        /// Gets the referer param.
 450        /// </summary>
 451        /// <param name="state">The state.</param>
 452        /// <returns>System.String.</returns>
 453        public string GetRefererParam(EncodingJobInfo state)
 454        {
 0455            if (state.RemoteHttpHeaders.TryGetValue("Referer", out string referer))
 456            {
 0457                return "-referer \"" + referer + "\"";
 458            }
 459
 0460            return string.Empty;
 461        }
 462
 463        public static string GetInputFormat(string container)
 464        {
 0465            if (string.IsNullOrEmpty(container) || !_validationRegex.IsMatch(container))
 466            {
 0467                return null;
 468            }
 469
 0470            container = container.Replace("mkv", "matroska", StringComparison.OrdinalIgnoreCase);
 471
 0472            if (string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
 473            {
 0474                return "mpegts";
 475            }
 476
 477            // For these need to find out the ffmpeg names
 0478            if (string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase))
 479            {
 0480                return null;
 481            }
 482
 0483            if (string.Equals(container, "wmv", StringComparison.OrdinalIgnoreCase))
 484            {
 0485                return null;
 486            }
 487
 0488            if (string.Equals(container, "mts", StringComparison.OrdinalIgnoreCase))
 489            {
 0490                return null;
 491            }
 492
 0493            if (string.Equals(container, "vob", StringComparison.OrdinalIgnoreCase))
 494            {
 0495                return null;
 496            }
 497
 0498            if (string.Equals(container, "mpg", StringComparison.OrdinalIgnoreCase))
 499            {
 0500                return null;
 501            }
 502
 0503            if (string.Equals(container, "mpeg", StringComparison.OrdinalIgnoreCase))
 504            {
 0505                return null;
 506            }
 507
 0508            if (string.Equals(container, "rec", StringComparison.OrdinalIgnoreCase))
 509            {
 0510                return null;
 511            }
 512
 0513            if (string.Equals(container, "dvr-ms", StringComparison.OrdinalIgnoreCase))
 514            {
 0515                return null;
 516            }
 517
 0518            if (string.Equals(container, "ogm", StringComparison.OrdinalIgnoreCase))
 519            {
 0520                return null;
 521            }
 522
 0523            if (string.Equals(container, "divx", StringComparison.OrdinalIgnoreCase))
 524            {
 0525                return null;
 526            }
 527
 0528            if (string.Equals(container, "tp", StringComparison.OrdinalIgnoreCase))
 529            {
 0530                return null;
 531            }
 532
 0533            if (string.Equals(container, "rmvb", StringComparison.OrdinalIgnoreCase))
 534            {
 0535                return null;
 536            }
 537
 0538            if (string.Equals(container, "rtp", StringComparison.OrdinalIgnoreCase))
 539            {
 0540                return null;
 541            }
 542
 543            // Seeing reported failures here, not sure yet if this is related to specifying input format
 0544            if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase))
 545            {
 0546                return null;
 547            }
 548
 549            // obviously don't do this for strm files
 0550            if (string.Equals(container, "strm", StringComparison.OrdinalIgnoreCase))
 551            {
 0552                return null;
 553            }
 554
 555            // ISO files don't have an ffmpeg format
 0556            if (string.Equals(container, "iso", StringComparison.OrdinalIgnoreCase))
 557            {
 0558                return null;
 559            }
 560
 0561            return container;
 562        }
 563
 564        /// <summary>
 565        /// Gets decoder from a codec.
 566        /// </summary>
 567        /// <param name="codec">Codec to use.</param>
 568        /// <returns>Decoder string.</returns>
 569        public string GetDecoderFromCodec(string codec)
 570        {
 571            // For these need to find out the ffmpeg names
 0572            if (string.Equals(codec, "mp2", StringComparison.OrdinalIgnoreCase))
 573            {
 0574                return null;
 575            }
 576
 0577            if (string.Equals(codec, "aac_latm", StringComparison.OrdinalIgnoreCase))
 578            {
 0579                return null;
 580            }
 581
 0582            if (string.Equals(codec, "eac3", StringComparison.OrdinalIgnoreCase))
 583            {
 0584                return null;
 585            }
 586
 0587            if (_mediaEncoder.SupportsDecoder(codec))
 588            {
 0589                return codec;
 590            }
 591
 0592            return null;
 593        }
 594
 595        /// <summary>
 596        /// Infers the audio codec based on the url.
 597        /// </summary>
 598        /// <param name="container">Container to use.</param>
 599        /// <returns>Codec string.</returns>
 600        public string InferAudioCodec(string container)
 601        {
 0602            var ext = "." + (container ?? string.Empty);
 603
 0604            if (string.Equals(ext, ".mp3", StringComparison.OrdinalIgnoreCase))
 605            {
 0606                return "mp3";
 607            }
 608
 0609            if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase))
 610            {
 0611                return "aac";
 612            }
 613
 0614            if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase))
 615            {
 0616                return "wma";
 617            }
 618
 0619            if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase))
 620            {
 0621                return "vorbis";
 622            }
 623
 0624            if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase))
 625            {
 0626                return "vorbis";
 627            }
 628
 0629            if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
 630            {
 0631                return "vorbis";
 632            }
 633
 0634            if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
 635            {
 0636                return "vorbis";
 637            }
 638
 0639            if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase))
 640            {
 0641                return "vorbis";
 642            }
 643
 0644            return "copy";
 645        }
 646
 647        /// <summary>
 648        /// Infers the video codec.
 649        /// </summary>
 650        /// <param name="url">The URL.</param>
 651        /// <returns>System.Nullable{VideoCodecs}.</returns>
 652        public string InferVideoCodec(string url)
 653        {
 0654            var ext = Path.GetExtension(url.AsSpan());
 655
 0656            if (ext.Equals(".asf", StringComparison.OrdinalIgnoreCase))
 657            {
 0658                return "wmv";
 659            }
 660
 0661            if (ext.Equals(".webm", StringComparison.OrdinalIgnoreCase))
 662            {
 663                // TODO: this may not always mean VP8, as the codec ages
 0664                return "vp8";
 665            }
 666
 0667            if (ext.Equals(".ogg", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ogv", StringComparison.OrdinalIgn
 668            {
 0669                return "theora";
 670            }
 671
 0672            if (ext.Equals(".m3u8", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ts", StringComparison.OrdinalIgn
 673            {
 0674                return "h264";
 675            }
 676
 0677            return "copy";
 678        }
 679
 680        public int GetVideoProfileScore(string videoCodec, string videoProfile)
 681        {
 682            // strip spaces because they may be stripped out on the query string
 0683            string profile = videoProfile.Replace(" ", string.Empty, StringComparison.Ordinal);
 0684            if (string.Equals("h264", videoCodec, StringComparison.OrdinalIgnoreCase))
 685            {
 0686                return Array.FindIndex(_videoProfilesH264, x => string.Equals(x, profile, StringComparison.OrdinalIgnore
 687            }
 688
 0689            if (string.Equals("hevc", videoCodec, StringComparison.OrdinalIgnoreCase))
 690            {
 0691                return Array.FindIndex(_videoProfilesH265, x => string.Equals(x, profile, StringComparison.OrdinalIgnore
 692            }
 693
 0694            if (string.Equals("av1", videoCodec, StringComparison.OrdinalIgnoreCase))
 695            {
 0696                return Array.FindIndex(_videoProfilesAv1, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreC
 697            }
 698
 0699            return -1;
 700        }
 701
 702        /// <summary>
 703        /// Gets the audio encoder.
 704        /// </summary>
 705        /// <param name="state">The state.</param>
 706        /// <returns>System.String.</returns>
 707        public string GetAudioEncoder(EncodingJobInfo state)
 708        {
 0709            var codec = state.OutputAudioCodec;
 710
 0711            if (!_validationRegex.IsMatch(codec))
 712            {
 0713                codec = "aac";
 714            }
 715
 0716            if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
 717            {
 718                // Use Apple's aac encoder if available as it provides best audio quality
 0719                if (_mediaEncoder.SupportsEncoder("aac_at"))
 720                {
 0721                    return "aac_at";
 722                }
 723
 724                // Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
 0725                if (_mediaEncoder.SupportsEncoder("libfdk_aac"))
 726                {
 0727                    return "libfdk_aac";
 728                }
 729
 0730                return "aac";
 731            }
 732
 0733            if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
 734            {
 0735                return "libmp3lame";
 736            }
 737
 0738            if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase))
 739            {
 0740                return "libvorbis";
 741            }
 742
 0743            if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
 744            {
 0745                return "libopus";
 746            }
 747
 0748            if (string.Equals(codec, "flac", StringComparison.OrdinalIgnoreCase))
 749            {
 0750                return "flac";
 751            }
 752
 0753            if (string.Equals(codec, "dts", StringComparison.OrdinalIgnoreCase))
 754            {
 0755                return "dca";
 756            }
 757
 0758            if (string.Equals(codec, "alac", StringComparison.OrdinalIgnoreCase))
 759            {
 760                // The ffmpeg upstream breaks the AudioToolbox ALAC encoder in version 6.1 but fixes it in version 7.0.
 761                // Since ALAC is lossless in quality and the AudioToolbox encoder is not faster,
 762                // its only benefit is a smaller file size.
 763                // To prevent problems, use the ffmpeg native encoder instead.
 0764                return "alac";
 765            }
 766
 0767            return codec.ToLowerInvariant();
 768        }
 769
 770        private string GetRkmppDeviceArgs(string alias)
 771        {
 0772            alias ??= RkmppAlias;
 773
 774            // device selection in rk is not supported.
 0775            return " -init_hw_device rkmpp=" + alias;
 776        }
 777
 778        private string GetVideoToolboxDeviceArgs(string alias)
 779        {
 0780            alias ??= VideotoolboxAlias;
 781
 782            // device selection in vt is not supported.
 0783            return " -init_hw_device videotoolbox=" + alias;
 784        }
 785
 786        private string GetCudaDeviceArgs(int deviceIndex, string alias)
 787        {
 0788            alias ??= CudaAlias;
 0789            deviceIndex = deviceIndex >= 0
 0790                ? deviceIndex
 0791                : 0;
 792
 0793            return string.Format(
 0794                CultureInfo.InvariantCulture,
 0795                " -init_hw_device cuda={0}:{1}",
 0796                alias,
 0797                deviceIndex);
 798        }
 799
 800        private string GetVulkanDeviceArgs(int deviceIndex, string deviceName, string srcDeviceAlias, string alias)
 801        {
 0802            alias ??= VulkanAlias;
 0803            deviceIndex = deviceIndex >= 0
 0804                ? deviceIndex
 0805                : 0;
 0806            var vendorOpts = string.IsNullOrEmpty(deviceName)
 0807                ? ":" + deviceIndex
 0808                : ":" + "\"" + deviceName + "\"";
 0809            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0810                ? vendorOpts
 0811                : "@" + srcDeviceAlias;
 812
 0813            return string.Format(
 0814                CultureInfo.InvariantCulture,
 0815                " -init_hw_device vulkan={0}{1}",
 0816                alias,
 0817                options);
 818        }
 819
 820        private string GetOpenclDeviceArgs(int deviceIndex, string deviceVendorName, string srcDeviceAlias, string alias
 821        {
 0822            alias ??= OpenclAlias;
 0823            deviceIndex = deviceIndex >= 0
 0824                ? deviceIndex
 0825                : 0;
 0826            var vendorOpts = string.IsNullOrEmpty(deviceVendorName)
 0827                ? ":0.0"
 0828                : ":." + deviceIndex + ",device_vendor=\"" + deviceVendorName + "\"";
 0829            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0830                ? vendorOpts
 0831                : "@" + srcDeviceAlias;
 832
 0833            return string.Format(
 0834                CultureInfo.InvariantCulture,
 0835                " -init_hw_device opencl={0}{1}",
 0836                alias,
 0837                options);
 838        }
 839
 840        private string GetD3d11vaDeviceArgs(int deviceIndex, string deviceVendorId, string alias)
 841        {
 0842            alias ??= D3d11vaAlias;
 0843            deviceIndex = deviceIndex >= 0 ? deviceIndex : 0;
 0844            var options = string.IsNullOrEmpty(deviceVendorId)
 0845                ? deviceIndex.ToString(CultureInfo.InvariantCulture)
 0846                : ",vendor=" + deviceVendorId;
 847
 0848            return string.Format(
 0849                CultureInfo.InvariantCulture,
 0850                " -init_hw_device d3d11va={0}:{1}",
 0851                alias,
 0852                options);
 853        }
 854
 855        private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string srcDeviceAli
 856        {
 0857            alias ??= VaapiAlias;
 858
 859            // 'renderNodePath' has higher priority than 'kernelDriver'
 0860            var driverOpts = string.IsNullOrEmpty(renderNodePath)
 0861                ? (string.IsNullOrEmpty(kernelDriver) ? string.Empty : ",kernel_driver=" + kernelDriver)
 0862                : renderNodePath;
 863
 864            // 'driver' behaves similarly to env LIBVA_DRIVER_NAME
 0865            driverOpts += string.IsNullOrEmpty(driver) ? string.Empty : ",driver=" + driver;
 866
 0867            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0868                ? (string.IsNullOrEmpty(driverOpts) ? string.Empty : ":" + driverOpts)
 0869                : "@" + srcDeviceAlias;
 870
 0871            return string.Format(
 0872                CultureInfo.InvariantCulture,
 0873                " -init_hw_device vaapi={0}{1}",
 0874                alias,
 0875                options);
 876        }
 877
 878        private string GetDrmDeviceArgs(string renderNodePath, string alias)
 879        {
 0880            alias ??= DrmAlias;
 0881            renderNodePath = renderNodePath ?? "/dev/dri/renderD128";
 882
 0883            return string.Format(
 0884                CultureInfo.InvariantCulture,
 0885                " -init_hw_device drm={0}:{1}",
 0886                alias,
 0887                renderNodePath);
 888        }
 889
 890        private string GetQsvDeviceArgs(string renderNodePath, string alias)
 891        {
 0892            var arg = " -init_hw_device qsv=" + (alias ?? QsvAlias);
 0893            if (OperatingSystem.IsLinux())
 894            {
 895                // derive qsv from vaapi device
 0896                return GetVaapiDeviceArgs(renderNodePath, "iHD", "i915", null, VaapiAlias) + arg + "@" + VaapiAlias;
 897            }
 898
 0899            if (OperatingSystem.IsWindows())
 900            {
 901                // on Windows, the deviceIndex is an int
 0902                if (int.TryParse(renderNodePath, NumberStyles.Integer, CultureInfo.InvariantCulture, out int deviceIndex
 903                {
 0904                    return GetD3d11vaDeviceArgs(deviceIndex, string.Empty, D3d11vaAlias) + arg + "@" + D3d11vaAlias;
 905                }
 906
 907                // derive qsv from d3d11va device
 0908                return GetD3d11vaDeviceArgs(0, "0x8086", D3d11vaAlias) + arg + "@" + D3d11vaAlias;
 909            }
 910
 0911            return null;
 912        }
 913
 914        private string GetFilterHwDeviceArgs(string alias)
 915        {
 0916            return string.IsNullOrEmpty(alias)
 0917                ? string.Empty
 0918                : " -filter_hw_device " + alias;
 919        }
 920
 921        public string GetGraphicalSubCanvasSize(EncodingJobInfo state)
 922        {
 923            // DVBSUB uses the fixed canvas size 720x576
 0924            if (state.SubtitleStream is not null
 0925                && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
 0926                && !state.SubtitleStream.IsTextSubtitleStream
 0927                && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 928            {
 0929                var subtitleWidth = state.SubtitleStream?.Width;
 0930                var subtitleHeight = state.SubtitleStream?.Height;
 931
 0932                if (subtitleWidth.HasValue
 0933                    && subtitleHeight.HasValue
 0934                    && subtitleWidth.Value > 0
 0935                    && subtitleHeight.Value > 0)
 936                {
 0937                    return string.Format(
 0938                        CultureInfo.InvariantCulture,
 0939                        " -canvas_size {0}x{1}",
 0940                        subtitleWidth.Value,
 0941                        subtitleHeight.Value);
 942                }
 943            }
 944
 0945            return string.Empty;
 946        }
 947
 948        /// <summary>
 949        /// Gets the input video hwaccel argument.
 950        /// </summary>
 951        /// <param name="state">Encoding state.</param>
 952        /// <param name="options">Encoding options.</param>
 953        /// <returns>Input video hwaccel arguments.</returns>
 954        public string GetInputVideoHwaccelArgs(EncodingJobInfo state, EncodingOptions options)
 955        {
 0956            if (!state.IsVideoRequest)
 957            {
 0958                return string.Empty;
 959            }
 960
 0961            var vidEncoder = GetVideoEncoder(state, options) ?? string.Empty;
 0962            if (IsCopyCodec(vidEncoder))
 963            {
 0964                return string.Empty;
 965            }
 966
 0967            var args = new StringBuilder();
 0968            var isWindows = OperatingSystem.IsWindows();
 0969            var isLinux = OperatingSystem.IsLinux();
 0970            var isMacOS = OperatingSystem.IsMacOS();
 0971            var optHwaccelType = options.HardwareAccelerationType;
 0972            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 0973            var isHwTonemapAvailable = IsHwTonemapAvailable(state, options);
 974
 0975            if (optHwaccelType == HardwareAccelerationType.vaapi)
 976            {
 0977                if (!isLinux || !_mediaEncoder.SupportsHwaccel("vaapi"))
 978                {
 0979                    return string.Empty;
 980                }
 981
 0982                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 0983                var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 0984                if (!isVaapiDecoder && !isVaapiEncoder)
 985                {
 0986                    return string.Empty;
 987                }
 988
 0989                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 990                {
 0991                    args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "iHD", null, null, VaapiAlias));
 992                }
 0993                else if (_mediaEncoder.IsVaapiDeviceInteli965)
 994                {
 995                    // Only override i965 since it has lower priority than iHD in libva lookup.
 0996                    Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME", "i965");
 0997                    Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME_JELLYFIN", "i965");
 0998                    args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "i965", null, null, VaapiAlias));
 999                }
 1000
 01001                var filterDevArgs = string.Empty;
 01002                var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
 1003
 01004                if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 1005                {
 01006                    if (doOclTonemap && !isVaapiDecoder)
 1007                    {
 01008                        args.Append(GetOpenclDeviceArgs(0, null, VaapiAlias, OpenclAlias));
 01009                        filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1010                    }
 1011                }
 01012                else if (_mediaEncoder.IsVaapiDeviceAmd)
 1013                {
 1014                    // Disable AMD EFC feature since it's still unstable in upstream Mesa.
 01015                    Environment.SetEnvironmentVariable("AMD_DEBUG", "noefc");
 1016
 01017                    if (IsVulkanFullSupported()
 01018                        && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 01019                        && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 1020                    {
 01021                        args.Append(GetDrmDeviceArgs(options.VaapiDevice, DrmAlias));
 01022                        args.Append(GetVaapiDeviceArgs(null, null, null, DrmAlias, VaapiAlias));
 01023                        args.Append(GetVulkanDeviceArgs(0, null, DrmAlias, VulkanAlias));
 1024
 1025                        // libplacebo wants an explicitly set vulkan filter device.
 01026                        filterDevArgs = GetFilterHwDeviceArgs(VulkanAlias);
 1027                    }
 1028                    else
 1029                    {
 01030                        args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, null, VaapiAlias));
 01031                        filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias);
 1032
 01033                        if (doOclTonemap)
 1034                        {
 1035                            // ROCm/ROCr OpenCL runtime
 01036                            args.Append(GetOpenclDeviceArgs(0, "Advanced Micro Devices", null, OpenclAlias));
 01037                            filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1038                        }
 1039                    }
 1040                }
 01041                else if (doOclTonemap)
 1042                {
 01043                    args.Append(GetOpenclDeviceArgs(0, null, null, OpenclAlias));
 01044                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1045                }
 1046
 01047                args.Append(filterDevArgs);
 1048            }
 01049            else if (optHwaccelType == HardwareAccelerationType.qsv)
 1050            {
 01051                if ((!isLinux && !isWindows) || !_mediaEncoder.SupportsHwaccel("qsv"))
 1052                {
 01053                    return string.Empty;
 1054                }
 1055
 01056                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 01057                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01058                var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 01059                var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 01060                var isHwDecoder = isQsvDecoder || isVaapiDecoder || isD3d11vaDecoder;
 01061                if (!isHwDecoder && !isQsvEncoder)
 1062                {
 01063                    return string.Empty;
 1064                }
 1065
 01066                args.Append(GetQsvDeviceArgs(options.QsvDevice, QsvAlias));
 01067                var filterDevArgs = GetFilterHwDeviceArgs(QsvAlias);
 1068                // child device used by qsv.
 01069                if (_mediaEncoder.SupportsHwaccel("vaapi") || _mediaEncoder.SupportsHwaccel("d3d11va"))
 1070                {
 01071                    if (isHwTonemapAvailable && IsOpenclFullSupported())
 1072                    {
 01073                        var srcAlias = isLinux ? VaapiAlias : D3d11vaAlias;
 01074                        args.Append(GetOpenclDeviceArgs(0, null, srcAlias, OpenclAlias));
 01075                        if (!isHwDecoder)
 1076                        {
 01077                            filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1078                        }
 1079                    }
 1080                }
 1081
 01082                args.Append(filterDevArgs);
 1083            }
 01084            else if (optHwaccelType == HardwareAccelerationType.nvenc)
 1085            {
 01086                if ((!isLinux && !isWindows) || !IsCudaFullSupported())
 1087                {
 01088                    return string.Empty;
 1089                }
 1090
 01091                var isCuvidDecoder = vidDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase);
 01092                var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 01093                var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 01094                var isHwDecoder = isNvdecDecoder || isCuvidDecoder;
 01095                if (!isHwDecoder && !isNvencEncoder)
 1096                {
 01097                    return string.Empty;
 1098                }
 1099
 01100                args.Append(GetCudaDeviceArgs(0, CudaAlias))
 01101                     .Append(GetFilterHwDeviceArgs(CudaAlias));
 1102            }
 01103            else if (optHwaccelType == HardwareAccelerationType.amf)
 1104            {
 01105                if (!isWindows || !_mediaEncoder.SupportsHwaccel("d3d11va"))
 1106                {
 01107                    return string.Empty;
 1108                }
 1109
 01110                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 01111                var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 01112                if (!isD3d11vaDecoder && !isAmfEncoder)
 1113                {
 01114                    return string.Empty;
 1115                }
 1116
 1117                // no dxva video processor hw filter.
 01118                args.Append(GetD3d11vaDeviceArgs(0, "0x1002", D3d11vaAlias));
 01119                var filterDevArgs = string.Empty;
 01120                if (IsOpenclFullSupported())
 1121                {
 01122                    args.Append(GetOpenclDeviceArgs(0, null, D3d11vaAlias, OpenclAlias));
 01123                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1124                }
 1125
 01126                args.Append(filterDevArgs);
 1127            }
 01128            else if (optHwaccelType == HardwareAccelerationType.videotoolbox)
 1129            {
 01130                if (!isMacOS || !_mediaEncoder.SupportsHwaccel("videotoolbox"))
 1131                {
 01132                    return string.Empty;
 1133                }
 1134
 01135                var isVideotoolboxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 01136                var isVideotoolboxEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 01137                if (!isVideotoolboxDecoder && !isVideotoolboxEncoder)
 1138                {
 01139                    return string.Empty;
 1140                }
 1141
 1142                // videotoolbox hw filter does not require device selection
 01143                args.Append(GetVideoToolboxDeviceArgs(VideotoolboxAlias));
 1144            }
 01145            else if (optHwaccelType == HardwareAccelerationType.rkmpp)
 1146            {
 01147                if (!isLinux || !_mediaEncoder.SupportsHwaccel("rkmpp"))
 1148                {
 01149                    return string.Empty;
 1150                }
 1151
 01152                var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 01153                var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 01154                if (!isRkmppDecoder && !isRkmppEncoder)
 1155                {
 01156                    return string.Empty;
 1157                }
 1158
 01159                args.Append(GetRkmppDeviceArgs(RkmppAlias));
 1160
 01161                var filterDevArgs = string.Empty;
 01162                var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
 1163
 01164                if (doOclTonemap && !isRkmppDecoder)
 1165                {
 01166                    args.Append(GetOpenclDeviceArgs(0, null, RkmppAlias, OpenclAlias));
 01167                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1168                }
 1169
 01170                args.Append(filterDevArgs);
 1171            }
 1172
 01173            if (!string.IsNullOrEmpty(vidDecoder))
 1174            {
 01175                args.Append(vidDecoder);
 1176            }
 1177
 01178            return args.ToString().Trim();
 1179        }
 1180
 1181        /// <summary>
 1182        /// Gets the input argument.
 1183        /// </summary>
 1184        /// <param name="state">Encoding state.</param>
 1185        /// <param name="options">Encoding options.</param>
 1186        /// <param name="segmentContainer">Segment Container.</param>
 1187        /// <returns>Input arguments.</returns>
 1188        public string GetInputArgument(EncodingJobInfo state, EncodingOptions options, string segmentContainer)
 1189        {
 01190            var arg = new StringBuilder();
 01191            var inputVidHwaccelArgs = GetInputVideoHwaccelArgs(state, options);
 1192
 01193            if (!string.IsNullOrEmpty(inputVidHwaccelArgs))
 1194            {
 01195                arg.Append(inputVidHwaccelArgs);
 1196            }
 1197
 01198            var canvasArgs = GetGraphicalSubCanvasSize(state);
 01199            if (!string.IsNullOrEmpty(canvasArgs))
 1200            {
 01201                arg.Append(canvasArgs);
 1202            }
 1203
 01204            if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
 1205            {
 01206                var concatFilePath = Path.Join(_configurationManager.CommonApplicationPaths.CachePath, "concat", state.M
 01207                if (!File.Exists(concatFilePath))
 1208                {
 01209                    _mediaEncoder.GenerateConcatConfig(state.MediaSource, concatFilePath);
 1210                }
 1211
 01212                arg.Append(" -f concat -safe 0 -i \"")
 01213                    .Append(concatFilePath)
 01214                    .Append("\" ");
 1215            }
 1216            else
 1217            {
 01218                arg.Append(" -i ")
 01219                    .Append(_mediaEncoder.GetInputPathArgument(state));
 1220            }
 1221
 1222            // sub2video for external graphical subtitles
 01223            if (state.SubtitleStream is not null
 01224                && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
 01225                && !state.SubtitleStream.IsTextSubtitleStream
 01226                && state.SubtitleStream.IsExternal)
 1227            {
 01228                var subtitlePath = state.SubtitleStream.Path;
 01229                var subtitleExtension = Path.GetExtension(subtitlePath.AsSpan());
 1230
 1231                // dvdsub/vobsub graphical subtitles use .sub+.idx pairs
 01232                if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase))
 1233                {
 01234                    var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
 01235                    if (File.Exists(idxFile))
 1236                    {
 01237                        subtitlePath = idxFile;
 1238                    }
 1239                }
 1240
 1241                // Also seek the external subtitles stream.
 01242                var seekSubParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01243                if (!string.IsNullOrEmpty(seekSubParam))
 1244                {
 01245                    arg.Append(' ').Append(seekSubParam);
 1246                }
 1247
 01248                if (!string.IsNullOrEmpty(canvasArgs))
 1249                {
 01250                    arg.Append(canvasArgs);
 1251                }
 1252
 01253                arg.Append(" -i file:\"").Append(subtitlePath).Append('\"');
 1254            }
 1255
 01256            if (state.AudioStream is not null && state.AudioStream.IsExternal)
 1257            {
 1258                // Also seek the external audio stream.
 01259                var seekAudioParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01260                if (!string.IsNullOrEmpty(seekAudioParam))
 1261                {
 01262                    arg.Append(' ').Append(seekAudioParam);
 1263                }
 1264
 01265                arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
 1266            }
 1267
 1268            // Disable auto inserted SW scaler for HW decoders in case of changed resolution.
 01269            var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
 01270            if (!isSwDecoder)
 1271            {
 01272                arg.Append(" -noautoscale");
 1273            }
 1274
 01275            return arg.ToString();
 1276        }
 1277
 1278        /// <summary>
 1279        /// Determines whether the specified stream is H264.
 1280        /// </summary>
 1281        /// <param name="stream">The stream.</param>
 1282        /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
 1283        public static bool IsH264(MediaStream stream)
 1284        {
 01285            var codec = stream.Codec ?? string.Empty;
 1286
 01287            return codec.Contains("264", StringComparison.OrdinalIgnoreCase)
 01288                    || codec.Contains("avc", StringComparison.OrdinalIgnoreCase);
 1289        }
 1290
 1291        public static bool IsH265(MediaStream stream)
 1292        {
 01293            var codec = stream.Codec ?? string.Empty;
 1294
 01295            return codec.Contains("265", StringComparison.OrdinalIgnoreCase)
 01296                || codec.Contains("hevc", StringComparison.OrdinalIgnoreCase);
 1297        }
 1298
 1299        public static bool IsAAC(MediaStream stream)
 1300        {
 01301            var codec = stream.Codec ?? string.Empty;
 1302
 01303            return codec.Contains("aac", StringComparison.OrdinalIgnoreCase);
 1304        }
 1305
 1306        public static string GetBitStreamArgs(MediaStream stream)
 1307        {
 1308            // TODO This is auto inserted into the mpegts mux so it might not be needed.
 1309            // https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
 01310            if (IsH264(stream))
 1311            {
 01312                return "-bsf:v h264_mp4toannexb";
 1313            }
 1314
 01315            if (IsH265(stream))
 1316            {
 01317                return "-bsf:v hevc_mp4toannexb";
 1318            }
 1319
 01320            if (IsAAC(stream))
 1321            {
 1322                // Convert adts header(mpegts) to asc header(mp4).
 01323                return "-bsf:a aac_adtstoasc";
 1324            }
 1325
 01326            return null;
 1327        }
 1328
 1329        public static string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSour
 1330        {
 01331            var bitStreamArgs = string.Empty;
 01332            var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
 1333
 1334            // Apply aac_adtstoasc bitstream filter when media source is in mpegts.
 01335            if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)
 01336                && (string.Equals(mediaSourceContainer, "ts", StringComparison.OrdinalIgnoreCase)
 01337                    || string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase)
 01338                    || string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase)))
 1339            {
 01340                bitStreamArgs = GetBitStreamArgs(state.AudioStream);
 01341                bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs;
 1342            }
 1343
 01344            return bitStreamArgs;
 1345        }
 1346
 1347        public static string GetSegmentFileExtension(string segmentContainer)
 1348        {
 01349            if (!string.IsNullOrWhiteSpace(segmentContainer))
 1350            {
 01351                return "." + segmentContainer;
 1352            }
 1353
 01354            return ".ts";
 1355        }
 1356
 1357        private string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
 1358        {
 01359            if (state.OutputVideoBitrate is null)
 1360            {
 01361                return string.Empty;
 1362            }
 1363
 01364            int bitrate = state.OutputVideoBitrate.Value;
 1365
 1366            // Bit rate under 1000k is not allowed in h264_qsv
 01367            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 1368            {
 01369                bitrate = Math.Max(bitrate, 1000);
 1370            }
 1371
 1372            // Currently use the same buffer size for all encoders
 01373            int bufsize = bitrate * 2;
 1374
 01375            if (string.Equals(videoCodec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1376            {
 01377                return FormattableString.Invariant($" -b:v {bitrate} -bufsize {bufsize}");
 1378            }
 1379
 01380            if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)
 01381                || string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
 1382            {
 01383                return FormattableString.Invariant($" -maxrate {bitrate} -bufsize {bufsize}");
 1384            }
 1385
 01386            if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01387                || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01388                || string.Equals(videoCodec, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1389            {
 1390                // Override the too high default qmin 18 in transcoding preset
 01391                return FormattableString.Invariant($" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsiz
 1392            }
 1393
 01394            if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01395                || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01396                || string.Equals(videoCodec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1397            {
 1398                // VBR in i965 driver may result in pixelated output.
 01399                if (_mediaEncoder.IsVaapiDeviceInteli965)
 1400                {
 01401                    return FormattableString.Invariant($" -rc_mode CBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsi
 1402                }
 1403
 01404                return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"
 1405            }
 1406
 01407            if (string.Equals(videoCodec, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 01408                || string.Equals(videoCodec, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase))
 1409            {
 1410                // The `maxrate` and `bufsize` options can potentially lead to performance regression
 1411                // and even encoder hangs, especially when the value is very high.
 01412                return FormattableString.Invariant($" -b:v {bitrate} -qmin -1 -qmax -1");
 1413            }
 1414
 01415            return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
 1416        }
 1417
 1418        private string GetEncoderParam(EncoderPreset? preset, EncoderPreset defaultPreset, EncodingOptions encodingOptio
 1419        {
 01420            var param = string.Empty;
 01421            var encoderPreset = preset ?? defaultPreset;
 01422            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
 1423            {
 01424                param += " -preset " + encoderPreset.ToString().ToLowerInvariant();
 1425
 01426                int encodeCrf = encodingOptions.H264Crf;
 01427                if (isLibX265)
 1428                {
 01429                    encodeCrf = encodingOptions.H265Crf;
 1430                }
 1431
 01432                if (encodeCrf >= 0 && encodeCrf <= 51)
 1433                {
 01434                    param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture);
 1435                }
 1436                else
 1437                {
 01438                    string defaultCrf = "23";
 01439                    if (isLibX265)
 1440                    {
 01441                        defaultCrf = "28";
 1442                    }
 1443
 01444                    param += " -crf " + defaultCrf;
 1445                }
 1446            }
 01447            else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1448            {
 1449                // Default to use the recommended preset 10.
 1450                // Omit presets < 5, which are too slow for on the fly encoding.
 1451                // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md
 01452                param += encoderPreset switch
 01453                {
 01454                    EncoderPreset.veryslow => " -preset 5",
 01455                    EncoderPreset.slower => " -preset 6",
 01456                    EncoderPreset.slow => " -preset 7",
 01457                    EncoderPreset.medium => " -preset 8",
 01458                    EncoderPreset.fast => " -preset 9",
 01459                    EncoderPreset.faster => " -preset 10",
 01460                    EncoderPreset.veryfast => " -preset 11",
 01461                    EncoderPreset.superfast => " -preset 12",
 01462                    EncoderPreset.ultrafast => " -preset 13",
 01463                    _ => " -preset 10"
 01464                };
 1465            }
 01466            else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01467                     || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01468                     || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1469            {
 1470                // -compression_level is not reliable on AMD.
 01471                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 1472                {
 01473                    param += encoderPreset switch
 01474                    {
 01475                        EncoderPreset.veryslow => " -compression_level 1",
 01476                        EncoderPreset.slower => " -compression_level 2",
 01477                        EncoderPreset.slow => " -compression_level 3",
 01478                        EncoderPreset.medium => " -compression_level 4",
 01479                        EncoderPreset.fast => " -compression_level 5",
 01480                        EncoderPreset.faster => " -compression_level 6",
 01481                        EncoderPreset.veryfast => " -compression_level 7",
 01482                        EncoderPreset.superfast => " -compression_level 7",
 01483                        EncoderPreset.ultrafast => " -compression_level 7",
 01484                        _ => string.Empty
 01485                    };
 1486                }
 1487            }
 01488            else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv)
 01489                     || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv)
 01490                     || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv)
 1491            {
 01492                EncoderPreset[] valid_presets = [EncoderPreset.veryslow, EncoderPreset.slower, EncoderPreset.slow, Encod
 1493
 01494                param += " -preset " + (valid_presets.Contains(encoderPreset) ? encoderPreset : EncoderPreset.veryfast).
 1495            }
 01496            else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
 01497                        || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_n
 01498                        || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase) // av1 (av1_nven
 01499            )
 1500            {
 01501                param += encoderPreset switch
 01502                {
 01503                        EncoderPreset.veryslow => " -preset p7",
 01504                        EncoderPreset.slower => " -preset p6",
 01505                        EncoderPreset.slow => " -preset p5",
 01506                        EncoderPreset.medium => " -preset p4",
 01507                        EncoderPreset.fast => " -preset p3",
 01508                        EncoderPreset.faster => " -preset p2",
 01509                        _ => " -preset p1"
 01510                };
 1511            }
 01512            else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf)
 01513                        || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf
 01514                        || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase) // av1 (av1_amf)
 01515            )
 1516            {
 01517                param += encoderPreset switch
 01518                {
 01519                        EncoderPreset.veryslow => " -quality quality",
 01520                        EncoderPreset.slower => " -quality quality",
 01521                        EncoderPreset.slow => " -quality quality",
 01522                        EncoderPreset.medium => " -quality balanced",
 01523                        _ => " -quality speed"
 01524                };
 1525
 01526                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01527                    || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1528                {
 01529                    param += " -header_insertion_mode gop";
 1530                }
 1531
 01532                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
 1533                {
 01534                    param += " -gops_per_idr 1";
 1535                }
 1536            }
 01537            else if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase) // h264 (h264_
 01538                        || string.Equals(videoEncoder, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase) // hevc 
 01539            )
 1540            {
 01541                param += encoderPreset switch
 01542                {
 01543                        EncoderPreset.veryslow => " -prio_speed 0",
 01544                        EncoderPreset.slower => " -prio_speed 0",
 01545                        EncoderPreset.slow => " -prio_speed 0",
 01546                        EncoderPreset.medium => " -prio_speed 0",
 01547                        _ => " -prio_speed 1"
 01548                };
 1549            }
 1550
 01551            return param;
 1552        }
 1553
 1554        public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
 1555        {
 01556            if (double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel))
 1557            {
 01558                if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase))
 1559                {
 1560                    // Transcode to level 5.3 (15) and lower for maximum compatibility.
 1561                    // https://en.wikipedia.org/wiki/AV1#Levels
 01562                    if (requestLevel < 0 || requestLevel >= 15)
 1563                    {
 01564                        return "15";
 1565                    }
 1566                }
 01567                else if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 01568                         || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
 1569                {
 1570                    // Transcode to level 5.0 and lower for maximum compatibility.
 1571                    // Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it.
 1572                    // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
 1573                    // MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880.
 01574                    if (requestLevel < 0 || requestLevel >= 150)
 1575                    {
 01576                        return "150";
 1577                    }
 1578                }
 01579                else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
 1580                {
 1581                    // Transcode to level 5.1 and lower for maximum compatibility.
 1582                    // h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
 1583                    // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
 01584                    if (requestLevel < 0 || requestLevel >= 51)
 1585                    {
 01586                        return "51";
 1587                    }
 1588                }
 1589            }
 1590
 01591            return level;
 1592        }
 1593
 1594        /// <summary>
 1595        /// Gets the text subtitle param.
 1596        /// </summary>
 1597        /// <param name="state">The state.</param>
 1598        /// <param name="enableAlpha">Enable alpha processing.</param>
 1599        /// <param name="enableSub2video">Enable sub2video mode.</param>
 1600        /// <returns>System.String.</returns>
 1601        public string GetTextSubtitlesFilter(EncodingJobInfo state, bool enableAlpha, bool enableSub2video)
 1602        {
 01603            var seconds = Math.Round(TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds);
 1604
 1605            // hls always copies timestamps
 01606            var setPtsParam = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive
 01607                ? string.Empty
 01608                : string.Format(CultureInfo.InvariantCulture, ",setpts=PTS -{0}/TB", seconds);
 1609
 01610            var alphaParam = enableAlpha ? ":alpha=1" : string.Empty;
 01611            var sub2videoParam = enableSub2video ? ":sub2video=1" : string.Empty;
 1612
 01613            var fontPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id);
 01614            var fontParam = string.Format(
 01615                CultureInfo.InvariantCulture,
 01616                ":fontsdir='{0}'",
 01617                _mediaEncoder.EscapeSubtitleFilterPath(fontPath));
 1618
 01619            if (state.SubtitleStream.IsExternal)
 1620            {
 01621                var charsetParam = string.Empty;
 1622
 01623                if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
 1624                {
 01625                    var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
 01626                            state.SubtitleStream,
 01627                            state.SubtitleStream.Language,
 01628                            state.MediaSource,
 01629                            CancellationToken.None).GetAwaiter().GetResult();
 1630
 01631                    if (!string.IsNullOrEmpty(charenc))
 1632                    {
 01633                        charsetParam = ":charenc=" + charenc;
 1634                    }
 1635                }
 1636
 01637                return string.Format(
 01638                    CultureInfo.InvariantCulture,
 01639                    "subtitles=f='{0}'{1}{2}{3}{4}{5}",
 01640                    _mediaEncoder.EscapeSubtitleFilterPath(state.SubtitleStream.Path),
 01641                    charsetParam,
 01642                    alphaParam,
 01643                    sub2videoParam,
 01644                    fontParam,
 01645                    setPtsParam);
 1646            }
 1647
 01648            var subtitlePath = _subtitleEncoder.GetSubtitleFilePath(
 01649                    state.SubtitleStream,
 01650                    state.MediaSource,
 01651                    CancellationToken.None).GetAwaiter().GetResult();
 1652
 01653            return string.Format(
 01654                CultureInfo.InvariantCulture,
 01655                "subtitles=f='{0}'{1}{2}{3}{4}",
 01656                _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
 01657                alphaParam,
 01658                sub2videoParam,
 01659                fontParam,
 01660                setPtsParam);
 1661        }
 1662
 1663        public double? GetFramerateParam(EncodingJobInfo state)
 1664        {
 01665            var request = state.BaseRequest;
 1666
 01667            if (request.Framerate.HasValue)
 1668            {
 01669                return request.Framerate.Value;
 1670            }
 1671
 01672            var maxrate = request.MaxFramerate;
 1673
 01674            if (maxrate.HasValue && state.VideoStream is not null)
 1675            {
 01676                var contentRate = state.VideoStream.ReferenceFrameRate;
 1677
 01678                if (contentRate.HasValue && contentRate.Value > maxrate.Value)
 1679                {
 01680                    return maxrate;
 1681                }
 1682            }
 1683
 01684            return null;
 1685        }
 1686
 1687        public string GetHlsVideoKeyFrameArguments(
 1688            EncodingJobInfo state,
 1689            string codec,
 1690            int segmentLength,
 1691            bool isEventPlaylist,
 1692            int? startNumber)
 1693        {
 01694            var args = string.Empty;
 01695            var gopArg = string.Empty;
 1696
 01697            var keyFrameArg = string.Format(
 01698                CultureInfo.InvariantCulture,
 01699                " -force_key_frames:0 \"expr:gte(t,n_forced*{0})\"",
 01700                segmentLength);
 1701
 01702            var framerate = state.VideoStream?.RealFrameRate;
 01703            if (framerate.HasValue)
 1704            {
 1705                // This is to make sure keyframe interval is limited to our segment,
 1706                // as forcing keyframes is not enough.
 1707                // Example: we encoded half of desired length, then codec detected
 1708                // scene cut and inserted a keyframe; next forced keyframe would
 1709                // be created outside of segment, which breaks seeking.
 01710                gopArg = string.Format(
 01711                    CultureInfo.InvariantCulture,
 01712                    " -g:v:0 {0} -keyint_min:v:0 {0}",
 01713                    Math.Ceiling(segmentLength * framerate.Value));
 1714            }
 1715
 1716            // Unable to force key frames using these encoders, set key frames by GOP.
 01717            if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01718                || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 01719                || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01720                || string.Equals(codec, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 01721                || string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 01722                || string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 01723                || string.Equals(codec, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase)
 01724                || string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 01725                || string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 01726                || string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase)
 01727                || string.Equals(codec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1728            {
 01729                args += gopArg;
 1730            }
 01731            else if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase)
 01732                     || string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)
 01733                     || string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01734                     || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01735                     || string.Equals(codec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1736            {
 01737                args += keyFrameArg;
 1738
 1739                // prevent the libx264 from post processing to break the set keyframe.
 01740                if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase))
 1741                {
 01742                    args += " -sc_threshold:v:0 0";
 1743                }
 1744            }
 1745            else
 1746            {
 01747                args += keyFrameArg + gopArg;
 1748            }
 1749
 1750            // global_header produced by AMD HEVC VA-API encoder causes non-playable fMP4 on iOS
 01751            if (string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01752                && _mediaEncoder.IsVaapiDeviceAmd)
 1753            {
 01754                args += " -flags:v -global_header";
 1755            }
 1756
 01757            return args;
 1758        }
 1759
 1760        /// <summary>
 1761        /// Gets the video bitrate to specify on the command line.
 1762        /// </summary>
 1763        /// <param name="state">Encoding state.</param>
 1764        /// <param name="videoEncoder">Video encoder to use.</param>
 1765        /// <param name="encodingOptions">Encoding options.</param>
 1766        /// <param name="defaultPreset">Default present to use for encoding.</param>
 1767        /// <returns>Video bitrate.</returns>
 1768        public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, 
 1769        {
 01770            var param = string.Empty;
 1771
 1772            // Tutorials: Enable Intel GuC / HuC firmware loading for Low Power Encoding.
 1773            // https://01.org/group/43/downloads/firmware
 1774            // https://wiki.archlinux.org/title/intel_graphics#Enable_GuC_/_HuC_firmware_loading
 1775            // Intel Low Power Encoding can save unnecessary CPU-GPU synchronization,
 1776            // which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
 01777            var intelLowPowerHwEncoding = false;
 1778
 1779            // Workaround for linux 5.18 to 6.1.3 i915 hang at cost of performance.
 1780            // https://github.com/intel/media-driver/issues/1456
 01781            var enableWaFori915Hang = false;
 1782
 01783            var hardwareAccelerationType = encodingOptions.HardwareAccelerationType;
 1784
 01785            if (hardwareAccelerationType == HardwareAccelerationType.vaapi)
 1786            {
 01787                var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
 1788
 01789                if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
 1790                {
 01791                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder && isIntelVaapiDriver;
 1792                }
 01793                else if (string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
 1794                {
 01795                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder && isIntelVaapiDriver;
 1796                }
 1797            }
 01798            else if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 1799            {
 01800                if (OperatingSystem.IsLinux())
 1801                {
 01802                    var ver = Environment.OSVersion.Version;
 01803                    var isFixedKernel60 = ver.Major == 6 && ver.Minor == 0 && ver >= _minFixedKernel60i915Hang;
 01804                    var isUnaffectedKernel = ver < _minKerneli915Hang || ver > _maxKerneli915Hang;
 1805
 01806                    if (!(isUnaffectedKernel || isFixedKernel60))
 1807                    {
 01808                        var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
 01809                        var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
 01810                                             || vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01811                        var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
 01812                            && IsVaapiSupported(state)
 01813                            && IsOpenclFullSupported()
 01814                            && !IsIntelVppTonemapAvailable(state, encodingOptions)
 01815                            && IsHwTonemapAvailable(state, encodingOptions);
 1816
 01817                        enableWaFori915Hang = isIntelDecoder && doOclTonemap;
 1818                    }
 1819                }
 1820
 01821                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 1822                {
 01823                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder;
 1824                }
 01825                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 1826                {
 01827                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder;
 1828                }
 1829                else
 1830                {
 01831                    enableWaFori915Hang = false;
 1832                }
 1833            }
 1834
 01835            if (intelLowPowerHwEncoding)
 1836            {
 01837                param += " -low_power 1";
 1838            }
 1839
 01840            if (enableWaFori915Hang)
 1841            {
 01842                param += " -async_depth 1";
 1843            }
 1844
 01845            var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
 01846            var encodingPreset = encodingOptions.EncoderPreset;
 1847
 01848            param += GetEncoderParam(encodingPreset, defaultPreset, encodingOptions, videoEncoder, isLibX265);
 01849            param += GetVideoBitrateParam(state, videoEncoder);
 1850
 01851            var framerate = GetFramerateParam(state);
 01852            if (framerate.HasValue)
 1853            {
 01854                param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(CultureInfo.Inv
 1855            }
 1856
 01857            var targetVideoCodec = state.ActualOutputVideoCodec;
 01858            if (string.Equals(targetVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
 01859                || string.Equals(targetVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
 1860            {
 01861                targetVideoCodec = "hevc";
 1862            }
 1863
 01864            var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
 01865            profile = WhiteSpaceRegex().Replace(profile, string.Empty).ToLowerInvariant();
 1866
 01867            var videoProfiles = Array.Empty<string>();
 01868            if (string.Equals("h264", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 1869            {
 01870                videoProfiles = _videoProfilesH264;
 1871            }
 01872            else if (string.Equals("hevc", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 1873            {
 01874                videoProfiles = _videoProfilesH265;
 1875            }
 01876            else if (string.Equals("av1", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 1877            {
 01878                videoProfiles = _videoProfilesAv1;
 1879            }
 1880
 01881            if (!videoProfiles.Contains(profile, StringComparison.OrdinalIgnoreCase))
 1882            {
 01883                profile = string.Empty;
 1884            }
 1885
 1886            // We only transcode to HEVC 8-bit for now, force Main Profile.
 01887            if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)
 01888                || profile.Contains("mainstill", StringComparison.OrdinalIgnoreCase))
 1889            {
 01890                profile = "main";
 1891            }
 1892
 1893            // Extended Profile is not supported by any known h264 encoders, force Main Profile.
 01894            if (profile.Contains("extended", StringComparison.OrdinalIgnoreCase))
 1895            {
 01896                profile = "main";
 1897            }
 1898
 1899            // Only libx264 support encoding H264 High 10 Profile, otherwise force High Profile.
 01900            if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 01901                && profile.Contains("high10", StringComparison.OrdinalIgnoreCase))
 1902            {
 01903                profile = "high";
 1904            }
 1905
 1906            // We only need Main profile of AV1 encoders.
 01907            if (videoEncoder.Contains("av1", StringComparison.OrdinalIgnoreCase)
 01908                && (profile.Contains("high", StringComparison.OrdinalIgnoreCase)
 01909                    || profile.Contains("professional", StringComparison.OrdinalIgnoreCase)))
 1910            {
 01911                profile = "main";
 1912            }
 1913
 1914            // h264_vaapi does not support Baseline profile, force Constrained Baseline in this case,
 1915            // which is compatible (and ugly).
 01916            if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01917                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 1918            {
 01919                profile = "constrained_baseline";
 1920            }
 1921
 1922            // libx264, h264_{qsv,nvenc,rkmpp} does not support Constrained Baseline profile, force Baseline in this cas
 01923            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 01924                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01925                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 01926                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 01927                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 1928            {
 01929                profile = "baseline";
 1930            }
 1931
 1932            // libx264, h264_{qsv,nvenc,vaapi,rkmpp} does not support Constrained High profile, force High in this case.
 01933            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 01934                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01935                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 01936                 || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01937                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 01938                && profile.Contains("high", StringComparison.OrdinalIgnoreCase))
 1939            {
 01940                profile = "high";
 1941            }
 1942
 01943            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01944                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 1945            {
 01946                profile = "constrained_baseline";
 1947            }
 1948
 01949            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01950                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 1951            {
 01952                profile = "constrained_high";
 1953            }
 1954
 01955            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 01956                && profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase))
 1957            {
 01958                profile = "constrained_baseline";
 1959            }
 1960
 01961            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 01962                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 1963            {
 01964                profile = "constrained_high";
 1965            }
 1966
 01967            if (!string.IsNullOrEmpty(profile))
 1968            {
 1969                // Currently there's no profile option in av1_nvenc encoder
 01970                if (!(string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 01971                      || string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)))
 1972                {
 01973                    param += " -profile:v:0 " + profile;
 1974                }
 1975            }
 1976
 01977            var level = state.GetRequestedLevel(targetVideoCodec);
 1978
 01979            if (!string.IsNullOrEmpty(level))
 1980            {
 01981                level = NormalizeTranscodingLevel(state, level);
 1982
 1983                // libx264, QSV, AMF can adjust the given level to match the output.
 01984                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01985                    || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 1986                {
 01987                    param += " -level " + level;
 1988                }
 01989                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 1990                {
 1991                    // hevc_qsv use -level 51 instead of -level 153.
 01992                    if (double.TryParse(level, CultureInfo.InvariantCulture, out double hevcLevel))
 1993                    {
 01994                        param += " -level " + (hevcLevel / 3);
 1995                    }
 1996                }
 01997                else if (string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 01998                         || string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1999                {
 2000                    // libsvtav1 and av1_qsv use -level 60 instead of -level 16
 2001                    // https://aomedia.org/av1/specification/annex-a/
 02002                    if (int.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out int av1Level))
 2003                    {
 02004                        var x = 2 + (av1Level >> 2);
 02005                        var y = av1Level & 3;
 02006                        var res = (x * 10) + y;
 02007                        param += " -level " + res;
 2008                    }
 2009                }
 02010                else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02011                         || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 02012                         || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 2013                {
 02014                    param += " -level " + level;
 2015                }
 02016                else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02017                         || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 02018                         || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase))
 2019                {
 2020                    // level option may cause NVENC to fail.
 2021                    // NVENC cannot adjust the given level, just throw an error.
 2022                }
 02023                else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02024                         || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02025                         || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 2026                {
 2027                    // level option may cause corrupted frames on AMD VAAPI.
 02028                    if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 2029                    {
 02030                        param += " -level " + level;
 2031                    }
 2032                }
 02033                else if (string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 02034                         || string.Equals(videoEncoder, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase))
 2035                {
 02036                    param += " -level " + level;
 2037                }
 02038                else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2039                {
 02040                    param += " -level " + level;
 2041                }
 2042            }
 2043
 02044            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2045            {
 02046                param += " -x264opts:0 subme=0:me_range=16:rc_lookahead=10:me=hex:open_gop=0";
 2047            }
 2048
 02049            if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2050            {
 2051                // libx265 only accept level option in -x265-params.
 2052                // level option may cause libx265 to fail.
 2053                // libx265 cannot adjust the given level, just throw an error.
 02054                param += " -x265-params:0 subme=3:merange=25:rc-lookahead=10:me=star:ctu=32:max-tu-size=32:min-cu-size=1
 2055            }
 2056
 02057            if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)
 02058                && _mediaEncoder.EncoderVersion >= _minFFmpegSvtAv1Params)
 2059            {
 02060                param += " -svtav1-params:0 rc=1:tune=0:film-grain=0:enable-overlays=1:enable-tf=0";
 2061            }
 2062
 2063            /* Access unit too large: 8192 < 20880 error */
 02064            if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) ||
 02065                 string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) &&
 02066                 _mediaEncoder.EncoderVersion >= _minFFmpegVaapiH26xEncA53CcSei)
 2067            {
 02068                param += " -sei -a53_cc";
 2069            }
 2070
 02071            return param;
 2072        }
 2073
 2074        public bool CanStreamCopyVideo(EncodingJobInfo state, MediaStream videoStream)
 2075        {
 02076            var request = state.BaseRequest;
 2077
 02078            if (!request.AllowVideoStreamCopy)
 2079            {
 02080                return false;
 2081            }
 2082
 02083            if (videoStream.IsInterlaced
 02084                && state.DeInterlace(videoStream.Codec, false))
 2085            {
 02086                return false;
 2087            }
 2088
 02089            if (videoStream.IsAnamorphic ?? false)
 2090            {
 02091                if (request.RequireNonAnamorphic)
 2092                {
 02093                    return false;
 2094                }
 2095            }
 2096
 2097            // Can't stream copy if we're burning in subtitles
 02098            if (request.SubtitleStreamIndex.HasValue
 02099                && request.SubtitleStreamIndex.Value >= 0
 02100                && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
 2101            {
 02102                return false;
 2103            }
 2104
 02105            if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 02106                && videoStream.IsAVC.HasValue
 02107                && !videoStream.IsAVC.Value
 02108                && request.RequireAvc)
 2109            {
 02110                return false;
 2111            }
 2112
 2113            // Source and target codecs must match
 02114            if (string.IsNullOrEmpty(videoStream.Codec)
 02115                || (state.SupportedVideoCodecs.Length != 0
 02116                    && !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparison.OrdinalIgnoreCase)))
 2117            {
 02118                return false;
 2119            }
 2120
 02121            var requestedProfiles = state.GetRequestedProfiles(videoStream.Codec);
 2122
 2123            // If client is requesting a specific video profile, it must match the source
 02124            if (requestedProfiles.Length > 0)
 2125            {
 02126                if (string.IsNullOrEmpty(videoStream.Profile))
 2127                {
 2128                    // return false;
 2129                }
 2130
 02131                var requestedProfile = requestedProfiles[0];
 2132                // strip spaces because they may be stripped out on the query string as well
 02133                if (!string.IsNullOrEmpty(videoStream.Profile)
 02134                    && !requestedProfiles.Contains(videoStream.Profile.Replace(" ", string.Empty, StringComparison.Ordin
 2135                {
 02136                    var currentScore = GetVideoProfileScore(videoStream.Codec, videoStream.Profile);
 02137                    var requestedScore = GetVideoProfileScore(videoStream.Codec, requestedProfile);
 2138
 02139                    if (currentScore == -1 || currentScore > requestedScore)
 2140                    {
 02141                        return false;
 2142                    }
 2143                }
 2144            }
 2145
 02146            var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
 02147            if (requestedRangeTypes.Length > 0)
 2148            {
 02149                if (videoStream.VideoRangeType == VideoRangeType.Unknown)
 2150                {
 02151                    return false;
 2152                }
 2153
 2154                // DOVIWithHDR10 should be compatible with HDR10 supporting players. Same goes with HLG and of course SD
 2155
 02156                var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ord
 02157                var requestHasHLG = requestedRangeTypes.Contains(VideoRangeType.HLG.ToString(), StringComparison.Ordinal
 02158                var requestHasSDR = requestedRangeTypes.Contains(VideoRangeType.SDR.ToString(), StringComparison.Ordinal
 2159
 02160                if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreC
 02161                     && !((requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10)
 02162                            || (requestHasHLG && videoStream.VideoRangeType == VideoRangeType.DOVIWithHLG)
 02163                            || (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)))
 2164                {
 02165                    return false;
 2166                }
 2167            }
 2168
 2169            // Video width must fall within requested value
 02170            if (request.MaxWidth.HasValue
 02171                && (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
 2172            {
 02173                return false;
 2174            }
 2175
 2176            // Video height must fall within requested value
 02177            if (request.MaxHeight.HasValue
 02178                && (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value))
 2179            {
 02180                return false;
 2181            }
 2182
 2183            // Video framerate must fall within requested value
 02184            var requestedFramerate = request.MaxFramerate ?? request.Framerate;
 02185            if (requestedFramerate.HasValue)
 2186            {
 02187                var videoFrameRate = videoStream.ReferenceFrameRate;
 2188
 02189                if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value)
 2190                {
 02191                    return false;
 2192                }
 2193            }
 2194
 2195            // Video bitrate must fall within requested value
 02196            if (request.VideoBitRate.HasValue
 02197                && (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value))
 2198            {
 2199                // For LiveTV that has no bitrate, let's try copy if other conditions are met
 02200                if (string.IsNullOrWhiteSpace(request.LiveStreamId) || videoStream.BitRate.HasValue)
 2201                {
 02202                    return false;
 2203                }
 2204            }
 2205
 02206            var maxBitDepth = state.GetRequestedVideoBitDepth(videoStream.Codec);
 02207            if (maxBitDepth.HasValue)
 2208            {
 02209                if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > maxBitDepth.Value)
 2210                {
 02211                    return false;
 2212                }
 2213            }
 2214
 02215            var maxRefFrames = state.GetRequestedMaxRefFrames(videoStream.Codec);
 02216            if (maxRefFrames.HasValue
 02217                && videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > maxRefFrames.Value)
 2218            {
 02219                return false;
 2220            }
 2221
 2222            // If a specific level was requested, the source must match or be less than
 02223            var level = state.GetRequestedLevel(videoStream.Codec);
 02224            if (double.TryParse(level, CultureInfo.InvariantCulture, out var requestLevel))
 2225            {
 02226                if (!videoStream.Level.HasValue)
 2227                {
 2228                    // return false;
 2229                }
 2230
 02231                if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
 2232                {
 02233                    return false;
 2234                }
 2235            }
 2236
 02237            if (string.Equals(state.InputContainer, "avi", StringComparison.OrdinalIgnoreCase)
 02238                && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)
 02239                && !(videoStream.IsAVC ?? false))
 2240            {
 2241                // see Coach S01E01 - Kelly and the Professor(0).avi
 02242                return false;
 2243            }
 2244
 02245            return true;
 2246        }
 2247
 2248        public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, IEnumerable<string> supportedAudi
 2249        {
 02250            var request = state.BaseRequest;
 2251
 02252            if (!request.AllowAudioStreamCopy)
 2253            {
 02254                return false;
 2255            }
 2256
 02257            var maxBitDepth = state.GetRequestedAudioBitDepth(audioStream.Codec);
 02258            if (maxBitDepth.HasValue
 02259                && audioStream.BitDepth.HasValue
 02260                && audioStream.BitDepth.Value > maxBitDepth.Value)
 2261            {
 02262                return false;
 2263            }
 2264
 2265            // Source and target codecs must match
 02266            if (string.IsNullOrEmpty(audioStream.Codec)
 02267                || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparison.OrdinalIgnoreCase))
 2268            {
 02269                return false;
 2270            }
 2271
 2272            // Channels must fall within requested value
 02273            var channels = state.GetRequestedAudioChannels(audioStream.Codec);
 02274            if (channels.HasValue)
 2275            {
 02276                if (!audioStream.Channels.HasValue || audioStream.Channels.Value <= 0)
 2277                {
 02278                    return false;
 2279                }
 2280
 02281                if (audioStream.Channels.Value > channels.Value)
 2282                {
 02283                    return false;
 2284                }
 2285            }
 2286
 2287            // Sample rate must fall within requested value
 02288            if (request.AudioSampleRate.HasValue)
 2289            {
 02290                if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value <= 0)
 2291                {
 02292                    return false;
 2293                }
 2294
 02295                if (audioStream.SampleRate.Value > request.AudioSampleRate.Value)
 2296                {
 02297                    return false;
 2298                }
 2299            }
 2300
 2301            // Audio bitrate must fall within requested value
 02302            if (request.AudioBitRate.HasValue
 02303                && audioStream.BitRate.HasValue
 02304                && audioStream.BitRate.Value > request.AudioBitRate.Value)
 2305            {
 02306                return false;
 2307            }
 2308
 02309            return request.EnableAutoStreamCopy;
 2310        }
 2311
 2312        public int GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideo
 2313        {
 02314            var bitrate = request.VideoBitRate;
 2315
 02316            if (videoStream is not null)
 2317            {
 02318                var isUpscaling = request.Height.HasValue
 02319                    && videoStream.Height.HasValue
 02320                    && request.Height.Value > videoStream.Height.Value
 02321                    && request.Width.HasValue
 02322                    && videoStream.Width.HasValue
 02323                    && request.Width.Value > videoStream.Width.Value;
 2324
 2325                // Don't allow bitrate increases unless upscaling
 02326                if (!isUpscaling && bitrate.HasValue && videoStream.BitRate.HasValue)
 2327                {
 02328                    bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value);
 2329                }
 2330
 02331                if (bitrate.HasValue)
 2332                {
 02333                    var inputVideoCodec = videoStream.Codec;
 02334                    bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
 2335
 2336                    // If a max bitrate was requested, don't let the scaled bitrate exceed it
 02337                    if (request.VideoBitRate.HasValue)
 2338                    {
 02339                        bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
 2340                    }
 2341                }
 2342            }
 2343
 2344            // Cap the max target bitrate to intMax/2 to satisfy the bufsize=bitrate*2.
 02345            return Math.Min(bitrate ?? 0, int.MaxValue / 2);
 2346        }
 2347
 2348        private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
 2349        {
 2350            // these values were chosen from testing to improve low bitrate streams
 02351            if (sourceBitrate <= 2000000)
 2352            {
 02353                sourceBitrate = Convert.ToInt32(sourceBitrate * 2.5);
 2354            }
 02355            else if (sourceBitrate <= 3000000)
 2356            {
 02357                sourceBitrate *= 2;
 2358            }
 2359
 02360            var bitrate = Math.Min(sourceBitrate, requestedBitrate);
 2361
 02362            return bitrate;
 2363        }
 2364
 2365        private static double GetVideoBitrateScaleFactor(string codec)
 2366        {
 2367            // hevc & vp9 - 40% more efficient than h.264
 02368            if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 02369                || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
 02370                || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
 2371            {
 02372                return .6;
 2373            }
 2374
 2375            // av1 - 50% more efficient than h.264
 02376            if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 2377            {
 02378                return .5;
 2379            }
 2380
 02381            return 1;
 2382        }
 2383
 2384        private static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
 2385        {
 02386            var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
 02387            var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
 2388
 2389            // Don't scale the real bitrate lower than the requested bitrate
 02390            var scaleFactor = Math.Max(outputScaleFactor / inputScaleFactor, 1);
 2391
 02392            if (bitrate <= 500000)
 2393            {
 02394                scaleFactor = Math.Max(scaleFactor, 4);
 2395            }
 02396            else if (bitrate <= 1000000)
 2397            {
 02398                scaleFactor = Math.Max(scaleFactor, 3);
 2399            }
 02400            else if (bitrate <= 2000000)
 2401            {
 02402                scaleFactor = Math.Max(scaleFactor, 2.5);
 2403            }
 02404            else if (bitrate <= 3000000)
 2405            {
 02406                scaleFactor = Math.Max(scaleFactor, 2);
 2407            }
 2408
 02409            return Convert.ToInt32(scaleFactor * bitrate);
 2410        }
 2411
 2412        public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChanne
 2413        {
 02414            return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels);
 2415        }
 2416
 2417        public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudio
 2418        {
 02419            if (audioStream is null)
 2420            {
 02421                return null;
 2422            }
 2423
 02424            var inputChannels = audioStream.Channels ?? 0;
 02425            var outputChannels = outputAudioChannels ?? 0;
 02426            var bitrate = audioBitRate ?? int.MaxValue;
 2427
 02428            if (string.IsNullOrEmpty(audioCodec)
 02429                || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
 02430                || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
 02431                || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
 02432                || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
 02433                || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
 02434                || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
 2435            {
 02436                return (inputChannels, outputChannels) switch
 02437                {
 02438                    (>= 6, >= 6 or 0) => Math.Min(640000, bitrate),
 02439                    (> 0, > 0) => Math.Min(outputChannels * 128000, bitrate),
 02440                    (> 0, _) => Math.Min(inputChannels * 128000, bitrate),
 02441                    (_, _) => Math.Min(384000, bitrate)
 02442                };
 2443            }
 2444
 02445            if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
 02446                || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
 2447            {
 02448                return (inputChannels, outputChannels) switch
 02449                {
 02450                    (>= 6, >= 6 or 0) => Math.Min(768000, bitrate),
 02451                    (> 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
 02452                    (> 0, _) => Math.Min(inputChannels * 136000, bitrate),
 02453                    (_, _) => Math.Min(672000, bitrate)
 02454                };
 2455            }
 2456
 2457            // Empty bitrate area is not allow on iOS
 2458            // Default audio bitrate to 128K per channel if we don't have codec specific defaults
 2459            // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
 02460            return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
 2461        }
 2462
 2463        public string GetAudioVbrModeParam(string encoder, int bitrate, int channels)
 2464        {
 02465            var bitratePerChannel = bitrate / Math.Max(channels, 1);
 02466            if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
 2467            {
 02468                return " -vbr:a " + bitratePerChannel switch
 02469                {
 02470                    < 32000 => "1",
 02471                    < 48000 => "2",
 02472                    < 64000 => "3",
 02473                    < 96000 => "4",
 02474                    _ => "5"
 02475                };
 2476            }
 2477
 02478            if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase))
 2479            {
 2480                // lame's VBR is only good for a certain bitrate range
 2481                // For very low and very high bitrate, use abr mode
 02482                if (bitratePerChannel is < 122500 and > 48000)
 2483                {
 02484                    return " -qscale:a " + bitratePerChannel switch
 02485                    {
 02486                        < 64000 => "6",
 02487                        < 88000 => "4",
 02488                        < 112000 => "2",
 02489                        _ => "0"
 02490                    };
 2491                }
 2492
 02493                return " -abr:a 1" + " -b:a " + bitrate;
 2494            }
 2495
 02496            if (string.Equals(encoder, "aac_at", StringComparison.OrdinalIgnoreCase))
 2497            {
 2498                // aac_at's CVBR mode
 02499                return " -aac_at_mode:a 2" + " -b:a " + bitrate;
 2500            }
 2501
 02502            if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase))
 2503            {
 02504                return " -qscale:a " + bitratePerChannel switch
 02505                {
 02506                    < 40000 => "0",
 02507                    < 56000 => "2",
 02508                    < 80000 => "4",
 02509                    < 112000 => "6",
 02510                    _ => "8"
 02511                };
 2512            }
 2513
 02514            return null;
 2515        }
 2516
 2517        public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
 2518        {
 02519            var channels = state.OutputAudioChannels;
 2520
 02521            var filters = new List<string>();
 2522
 02523            if (channels is 2 && state.AudioStream?.Channels is > 2)
 2524            {
 02525                var hasDownMixFilter = DownMixAlgorithmsHelper.AlgorithmFilterStrings.TryGetValue((encodingOptions.DownM
 02526                if (hasDownMixFilter)
 2527                {
 02528                    filters.Add(downMixFilterString);
 2529                }
 2530
 02531                if (!encodingOptions.DownMixAudioBoost.Equals(1))
 2532                {
 02533                    filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
 2534                }
 2535            }
 2536
 02537            var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
 02538            if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryM
 2539            {
 02540                var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds;
 2541
 02542                filters.Add(
 02543                    string.Format(
 02544                        CultureInfo.InvariantCulture,
 02545                        "asetpts=PTS-{0}/TB",
 02546                        Math.Round(seconds)));
 2547            }
 2548
 02549            if (filters.Count > 0)
 2550            {
 02551                return " -af \"" + string.Join(',', filters) + "\"";
 2552            }
 2553
 02554            return string.Empty;
 2555        }
 2556
 2557        /// <summary>
 2558        /// Gets the number of audio channels to specify on the command line.
 2559        /// </summary>
 2560        /// <param name="state">The state.</param>
 2561        /// <param name="audioStream">The audio stream.</param>
 2562        /// <param name="outputAudioCodec">The output audio codec.</param>
 2563        /// <returns>System.Nullable{System.Int32}.</returns>
 2564        public int? GetNumAudioChannelsParam(EncodingJobInfo state, MediaStream audioStream, string outputAudioCodec)
 2565        {
 02566            if (audioStream is null)
 2567            {
 02568                return null;
 2569            }
 2570
 02571            var request = state.BaseRequest;
 2572
 02573            var codec = outputAudioCodec ?? string.Empty;
 2574
 02575            int? resultChannels = state.GetRequestedAudioChannels(codec);
 2576
 02577            var inputChannels = audioStream.Channels;
 2578
 02579            if (inputChannels > 0)
 2580            {
 02581                resultChannels = inputChannels < resultChannels ? inputChannels : resultChannels ?? inputChannels;
 2582            }
 2583
 02584            var isTranscodingAudio = !IsCopyCodec(codec);
 2585
 02586            if (isTranscodingAudio)
 2587            {
 02588                var audioEncoder = GetAudioEncoder(state);
 02589                if (!_audioTranscodeChannelLookup.TryGetValue(audioEncoder, out var transcoderChannelLimit))
 2590                {
 2591                    // Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many c
 02592                    transcoderChannelLimit = 8;
 2593                }
 2594
 2595                // Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelL
 02596                resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? tr
 2597
 02598                if (request.TranscodingMaxAudioChannels < resultChannels)
 2599                {
 02600                    resultChannels = request.TranscodingMaxAudioChannels;
 2601                }
 2602
 2603                // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
 2604                // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_d
 02605                if (state.TranscodingType != TranscodingJobType.Progressive
 02606                    && ((resultChannels > 2 && resultChannels < 6) || resultChannels == 7))
 2607                {
 2608                    // We can let FFMpeg supply an extra LFE channel for 5ch and 7ch to make them 5.1 and 7.1
 02609                    if (resultChannels == 5)
 2610                    {
 02611                        resultChannels = 6;
 2612                    }
 02613                    else if (resultChannels == 7)
 2614                    {
 02615                        resultChannels = 8;
 2616                    }
 2617                    else
 2618                    {
 2619                        // For other weird layout, just downmix to stereo for compatibility
 02620                        resultChannels = 2;
 2621                    }
 2622                }
 2623            }
 2624
 02625            return resultChannels;
 2626        }
 2627
 2628        /// <summary>
 2629        /// Enforces the resolution limit.
 2630        /// </summary>
 2631        /// <param name="state">The state.</param>
 2632        public void EnforceResolutionLimit(EncodingJobInfo state)
 2633        {
 02634            var videoRequest = state.BaseRequest;
 2635
 2636            // Switch the incoming params to be ceilings rather than fixed values
 02637            videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
 02638            videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
 2639
 02640            videoRequest.Width = null;
 02641            videoRequest.Height = null;
 02642        }
 2643
 2644        /// <summary>
 2645        /// Gets the fast seek command line parameter.
 2646        /// </summary>
 2647        /// <param name="state">The state.</param>
 2648        /// <param name="options">The options.</param>
 2649        /// <param name="segmentContainer">Segment Container.</param>
 2650        /// <returns>System.String.</returns>
 2651        /// <value>The fast seek command line parameter.</value>
 2652        public string GetFastSeekCommandLineParameter(EncodingJobInfo state, EncodingOptions options, string segmentCont
 2653        {
 02654            var time = state.BaseRequest.StartTimeTicks ?? 0;
 02655            var seekParam = string.Empty;
 2656
 02657            if (time > 0)
 2658            {
 2659                // For direct streaming/remuxing, we seek at the exact position of the keyframe
 2660                // However, ffmpeg will seek to previous keyframe when the exact time is the input
 2661                // Workaround this by adding 0.5s offset to the seeking time to get the exact keyframe on most videos.
 2662                // This will help subtitle syncing.
 02663                var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCod
 02664                var seekTick = isHlsRemuxing ? time + 5000000L : time;
 02665                seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(seekT
 2666
 02667                if (state.IsVideoRequest)
 2668                {
 02669                    var outputVideoCodec = GetVideoEncoder(state, options);
 02670                    var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
 2671
 2672                    // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking
 2673                    // Disable -noaccurate_seek on mpegts container due to the timestamps issue on some clients,
 2674                    // but it's still required for fMP4 container otherwise the audio can't be synced to the video.
 02675                    if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase)
 02676                        && !string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase)
 02677                        && state.TranscodingType != TranscodingJobType.Progressive
 02678                        && !state.EnableBreakOnNonKeyFrames(outputVideoCodec)
 02679                        && (state.BaseRequest.StartTimeTicks ?? 0) > 0)
 2680                    {
 02681                        seekParam += " -noaccurate_seek";
 2682                    }
 2683                }
 2684            }
 2685
 02686            return seekParam;
 2687        }
 2688
 2689        /// <summary>
 2690        /// Gets the map args.
 2691        /// </summary>
 2692        /// <param name="state">The state.</param>
 2693        /// <returns>System.String.</returns>
 2694        public string GetMapArgs(EncodingJobInfo state)
 2695        {
 2696            // If we don't have known media info
 2697            // If input is video, use -sn to drop subtitles
 2698            // Otherwise just return empty
 02699            if (state.VideoStream is null && state.AudioStream is null)
 2700            {
 02701                return state.IsInputVideo ? "-sn" : string.Empty;
 2702            }
 2703
 2704            // We have media info, but we don't know the stream index
 02705            if (state.VideoStream is not null && state.VideoStream.Index == -1)
 2706            {
 02707                return "-sn";
 2708            }
 2709
 2710            // We have media info, but we don't know the stream index
 02711            if (state.AudioStream is not null && state.AudioStream.Index == -1)
 2712            {
 02713                return state.IsInputVideo ? "-sn" : string.Empty;
 2714            }
 2715
 02716            var args = string.Empty;
 2717
 02718            if (state.VideoStream is not null)
 2719            {
 02720                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 2721
 02722                args += string.Format(
 02723                    CultureInfo.InvariantCulture,
 02724                    "-map 0:{0}",
 02725                    videoStreamIndex);
 2726            }
 2727            else
 2728            {
 2729                // No known video stream
 02730                args += "-vn";
 2731            }
 2732
 02733            if (state.AudioStream is not null)
 2734            {
 02735                int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
 02736                if (state.AudioStream.IsExternal)
 2737                {
 02738                    bool hasExternalGraphicsSubs = state.SubtitleStream is not null
 02739                        && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
 02740                        && state.SubtitleStream.IsExternal
 02741                        && !state.SubtitleStream.IsTextSubtitleStream;
 02742                    int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
 2743
 02744                    args += string.Format(
 02745                        CultureInfo.InvariantCulture,
 02746                        " -map {0}:{1}",
 02747                        externalAudioMapIndex,
 02748                        audioStreamIndex);
 2749                }
 2750                else
 2751                {
 02752                    args += string.Format(
 02753                        CultureInfo.InvariantCulture,
 02754                        " -map 0:{0}",
 02755                        audioStreamIndex);
 2756                }
 2757            }
 2758            else
 2759            {
 02760                args += " -map -0:a";
 2761            }
 2762
 02763            var subtitleMethod = state.SubtitleDeliveryMethod;
 02764            if (state.SubtitleStream is null || subtitleMethod == SubtitleDeliveryMethod.Hls)
 2765            {
 02766                args += " -map -0:s";
 2767            }
 02768            else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
 2769            {
 02770                int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 2771
 02772                args += string.Format(
 02773                    CultureInfo.InvariantCulture,
 02774                    " -map 0:{0}",
 02775                    subtitleStreamIndex);
 2776            }
 02777            else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
 2778            {
 02779                int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 2780
 02781                args += string.Format(
 02782                    CultureInfo.InvariantCulture,
 02783                    " -map 1:{0} -sn",
 02784                    externalSubtitleStreamIndex);
 2785            }
 2786
 02787            return args;
 2788        }
 2789
 2790        /// <summary>
 2791        /// Gets the negative map args by filters.
 2792        /// </summary>
 2793        /// <param name="state">The state.</param>
 2794        /// <param name="videoProcessFilters">The videoProcessFilters.</param>
 2795        /// <returns>System.String.</returns>
 2796        public string GetNegativeMapArgsByFilters(EncodingJobInfo state, string videoProcessFilters)
 2797        {
 02798            string args = string.Empty;
 2799
 2800            // http://ffmpeg.org/ffmpeg-all.html#toc-Complex-filtergraphs-1
 02801            if (state.VideoStream is not null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordina
 2802            {
 02803                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 2804
 02805                args += string.Format(
 02806                    CultureInfo.InvariantCulture,
 02807                    "-map -0:{0} ",
 02808                    videoStreamIndex);
 2809            }
 2810
 02811            return args;
 2812        }
 2813
 2814        /// <summary>
 2815        /// Determines which stream will be used for playback.
 2816        /// </summary>
 2817        /// <param name="allStream">All stream.</param>
 2818        /// <param name="desiredIndex">Index of the desired.</param>
 2819        /// <param name="type">The type.</param>
 2820        /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
 2821        /// <returns>MediaStream.</returns>
 2822        public MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, b
 2823        {
 02824            var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
 2825
 02826            if (desiredIndex.HasValue)
 2827            {
 02828                var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
 2829
 02830                if (stream is not null)
 2831                {
 02832                    return stream;
 2833                }
 2834            }
 2835
 02836            if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
 2837            {
 02838                return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
 02839                       streams.FirstOrDefault();
 2840            }
 2841
 2842            // Just return the first one
 02843            return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
 2844        }
 2845
 2846        public static (int? Width, int? Height) GetFixedOutputSize(
 2847            int? videoWidth,
 2848            int? videoHeight,
 2849            int? requestedWidth,
 2850            int? requestedHeight,
 2851            int? requestedMaxWidth,
 2852            int? requestedMaxHeight)
 2853        {
 02854            if (!videoWidth.HasValue && !requestedWidth.HasValue)
 2855            {
 02856                return (null, null);
 2857            }
 2858
 02859            if (!videoHeight.HasValue && !requestedHeight.HasValue)
 2860            {
 02861                return (null, null);
 2862            }
 2863
 02864            int inputWidth = Convert.ToInt32(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture);
 02865            int inputHeight = Convert.ToInt32(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture);
 02866            int outputWidth = requestedWidth ?? inputWidth;
 02867            int outputHeight = requestedHeight ?? inputHeight;
 2868
 2869            // Don't transcode video to bigger than 4k when using HW.
 02870            int maximumWidth = Math.Min(requestedMaxWidth ?? outputWidth, 4096);
 02871            int maximumHeight = Math.Min(requestedMaxHeight ?? outputHeight, 4096);
 2872
 02873            if (outputWidth > maximumWidth || outputHeight > maximumHeight)
 2874            {
 02875                var scaleW = (double)maximumWidth / outputWidth;
 02876                var scaleH = (double)maximumHeight / outputHeight;
 02877                var scale = Math.Min(scaleW, scaleH);
 02878                outputWidth = Math.Min(maximumWidth, Convert.ToInt32(outputWidth * scale));
 02879                outputHeight = Math.Min(maximumHeight, Convert.ToInt32(outputHeight * scale));
 2880            }
 2881
 02882            outputWidth = 2 * (outputWidth / 2);
 02883            outputHeight = 2 * (outputHeight / 2);
 2884
 02885            return (outputWidth, outputHeight);
 2886        }
 2887
 2888        public static bool IsScaleRatioSupported(
 2889            int? videoWidth,
 2890            int? videoHeight,
 2891            int? requestedWidth,
 2892            int? requestedHeight,
 2893            int? requestedMaxWidth,
 2894            int? requestedMaxHeight,
 2895            double? maxScaleRatio)
 2896        {
 02897            var (outWidth, outHeight) = GetFixedOutputSize(
 02898                videoWidth,
 02899                videoHeight,
 02900                requestedWidth,
 02901                requestedHeight,
 02902                requestedMaxWidth,
 02903                requestedMaxHeight);
 2904
 02905            if (!videoWidth.HasValue
 02906                 || !videoHeight.HasValue
 02907                 || !outWidth.HasValue
 02908                 || !outHeight.HasValue
 02909                 || !maxScaleRatio.HasValue
 02910                 || (maxScaleRatio.Value < 1.0f))
 2911            {
 02912                return false;
 2913            }
 2914
 02915            var minScaleRatio = 1.0f / maxScaleRatio;
 02916            var scaleRatioW = (double)outWidth / (double)videoWidth;
 02917            var scaleRatioH = (double)outHeight / (double)videoHeight;
 2918
 02919            if (scaleRatioW < minScaleRatio
 02920                || scaleRatioW > maxScaleRatio
 02921                || scaleRatioH < minScaleRatio
 02922                || scaleRatioH > maxScaleRatio)
 2923            {
 02924                return false;
 2925            }
 2926
 02927            return true;
 2928        }
 2929
 2930        public static string GetHwScaleFilter(
 2931            string hwScalePrefix,
 2932            string hwScaleSuffix,
 2933            string videoFormat,
 2934            bool swapOutputWandH,
 2935            int? videoWidth,
 2936            int? videoHeight,
 2937            int? requestedWidth,
 2938            int? requestedHeight,
 2939            int? requestedMaxWidth,
 2940            int? requestedMaxHeight)
 2941        {
 02942            var (outWidth, outHeight) = GetFixedOutputSize(
 02943                videoWidth,
 02944                videoHeight,
 02945                requestedWidth,
 02946                requestedHeight,
 02947                requestedMaxWidth,
 02948                requestedMaxHeight);
 2949
 02950            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 02951            var isSizeFixed = !videoWidth.HasValue
 02952                || outWidth.Value != videoWidth.Value
 02953                || !videoHeight.HasValue
 02954                || outHeight.Value != videoHeight.Value;
 2955
 02956            var swpOutW = swapOutputWandH ? outHeight.Value : outWidth.Value;
 02957            var swpOutH = swapOutputWandH ? outWidth.Value : outHeight.Value;
 2958
 02959            var arg1 = isSizeFixed ? $"=w={swpOutW}:h={swpOutH}" : string.Empty;
 02960            var arg2 = isFormatFixed ? $"format={videoFormat}" : string.Empty;
 02961            if (isFormatFixed)
 2962            {
 02963                arg2 = (isSizeFixed ? ':' : '=') + arg2;
 2964            }
 2965
 02966            if (!string.IsNullOrEmpty(hwScaleSuffix) && (isSizeFixed || isFormatFixed))
 2967            {
 02968                return string.Format(
 02969                    CultureInfo.InvariantCulture,
 02970                    "{0}_{1}{2}{3}",
 02971                    hwScalePrefix ?? "scale",
 02972                    hwScaleSuffix,
 02973                    arg1,
 02974                    arg2);
 2975            }
 2976
 02977            return string.Empty;
 2978        }
 2979
 2980        public static string GetGraphicalSubPreProcessFilters(
 2981            int? videoWidth,
 2982            int? videoHeight,
 2983            int? subtitleWidth,
 2984            int? subtitleHeight,
 2985            int? requestedWidth,
 2986            int? requestedHeight,
 2987            int? requestedMaxWidth,
 2988            int? requestedMaxHeight)
 2989        {
 02990            var (outWidth, outHeight) = GetFixedOutputSize(
 02991                videoWidth,
 02992                videoHeight,
 02993                requestedWidth,
 02994                requestedHeight,
 02995                requestedMaxWidth,
 02996                requestedMaxHeight);
 2997
 02998            if (!outWidth.HasValue
 02999                || !outHeight.HasValue
 03000                || outWidth.Value <= 0
 03001                || outHeight.Value <= 0)
 3002            {
 03003                return string.Empty;
 3004            }
 3005
 3006            // Automatically add padding based on subtitle input
 03007            var filters = @"scale,scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:blac
 3008
 03009            if (subtitleWidth.HasValue
 03010                && subtitleHeight.HasValue
 03011                && subtitleWidth.Value > 0
 03012                && subtitleHeight.Value > 0)
 3013            {
 03014                var videoDar = (double)outWidth.Value / outHeight.Value;
 03015                var subtitleDar = (double)subtitleWidth.Value / subtitleHeight.Value;
 3016
 3017                // No need to add padding when DAR is the same -> 1080p PGSSUB on 2160p video
 03018                if (Math.Abs(videoDar - subtitleDar) < 0.01f)
 3019                {
 03020                    filters = @"scale,scale={0}:{1}:fast_bilinear";
 3021                }
 3022            }
 3023
 03024            return string.Format(
 03025                CultureInfo.InvariantCulture,
 03026                filters,
 03027                outWidth.Value,
 03028                outHeight.Value);
 3029        }
 3030
 3031        public static string GetAlphaSrcFilter(
 3032            EncodingJobInfo state,
 3033            int? videoWidth,
 3034            int? videoHeight,
 3035            int? requestedWidth,
 3036            int? requestedHeight,
 3037            int? requestedMaxWidth,
 3038            int? requestedMaxHeight,
 3039            float? framerate)
 3040        {
 03041            var reqTicks = state.BaseRequest.StartTimeTicks ?? 0;
 03042            var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture
 03043            var (outWidth, outHeight) = GetFixedOutputSize(
 03044                videoWidth,
 03045                videoHeight,
 03046                requestedWidth,
 03047                requestedHeight,
 03048                requestedMaxWidth,
 03049                requestedMaxHeight);
 3050
 03051            if (outWidth.HasValue && outHeight.HasValue)
 3052            {
 03053                return string.Format(
 03054                    CultureInfo.InvariantCulture,
 03055                    "alphasrc=s={0}x{1}:r={2}:start='{3}'",
 03056                    outWidth.Value,
 03057                    outHeight.Value,
 03058                    framerate ?? 25,
 03059                    reqTicks > 0 ? startTime : 0);
 3060            }
 3061
 03062            return string.Empty;
 3063        }
 3064
 3065        public static string GetSwScaleFilter(
 3066            EncodingJobInfo state,
 3067            EncodingOptions options,
 3068            string videoEncoder,
 3069            int? videoWidth,
 3070            int? videoHeight,
 3071            Video3DFormat? threedFormat,
 3072            int? requestedWidth,
 3073            int? requestedHeight,
 3074            int? requestedMaxWidth,
 3075            int? requestedMaxHeight)
 3076        {
 03077            var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 03078            var isMjpeg = videoEncoder is not null && videoEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase)
 03079            var scaleVal = isV4l2 ? 64 : 2;
 03080            var targetAr = isMjpeg ? "(a*sar)" : "a"; // manually calculate AR when using mjpeg encoder
 3081
 3082            // If fixed dimensions were supplied
 03083            if (requestedWidth.HasValue && requestedHeight.HasValue)
 3084            {
 03085                if (isV4l2)
 3086                {
 03087                    var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 03088                    var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3089
 03090                    return string.Format(
 03091                            CultureInfo.InvariantCulture,
 03092                            "scale=trunc({0}/64)*64:trunc({1}/2)*2",
 03093                            widthParam,
 03094                            heightParam);
 3095                }
 3096
 03097                return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
 3098            }
 3099
 3100            // If Max dimensions were supplied, for width selects lowest even number between input width and width req s
 3101
 03102            if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
 3103            {
 03104                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 03105                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3106
 03107                return string.Format(
 03108                    CultureInfo.InvariantCulture,
 03109                    @"scale=trunc(min(max(iw\,ih*{3})\,min({0}\,{1}*{3}))/{2})*{2}:trunc(min(max(iw/{3}\,ih)\,min({0}/{3
 03110                    maxWidthParam,
 03111                    maxHeightParam,
 03112                    scaleVal,
 03113                    targetAr);
 3114            }
 3115
 3116            // If a fixed width was requested
 03117            if (requestedWidth.HasValue)
 3118            {
 03119                if (threedFormat.HasValue)
 3120                {
 3121                    // This method can handle 0 being passed in for the requested height
 03122                    return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0);
 3123                }
 3124
 03125                var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 3126
 03127                return string.Format(
 03128                    CultureInfo.InvariantCulture,
 03129                    "scale={0}:trunc(ow/{1}/2)*2",
 03130                    widthParam,
 03131                    targetAr);
 3132            }
 3133
 3134            // If a fixed height was requested
 03135            if (requestedHeight.HasValue)
 3136            {
 03137                var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3138
 03139                return string.Format(
 03140                    CultureInfo.InvariantCulture,
 03141                    "scale=trunc(oh*{2}/{1})*{1}:{0}",
 03142                    heightParam,
 03143                    scaleVal,
 03144                    targetAr);
 3145            }
 3146
 3147            // If a max width was requested
 03148            if (requestedMaxWidth.HasValue)
 3149            {
 03150                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 3151
 03152                return string.Format(
 03153                    CultureInfo.InvariantCulture,
 03154                    @"scale=trunc(min(max(iw\,ih*{2})\,{0})/{1})*{1}:trunc(ow/{2}/2)*2",
 03155                    maxWidthParam,
 03156                    scaleVal,
 03157                    targetAr);
 3158            }
 3159
 3160            // If a max height was requested
 03161            if (requestedMaxHeight.HasValue)
 3162            {
 03163                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3164
 03165                return string.Format(
 03166                    CultureInfo.InvariantCulture,
 03167                    @"scale=trunc(oh*{2}/{1})*{1}:min(max(iw/{2}\,ih)\,{0})",
 03168                    maxHeightParam,
 03169                    scaleVal,
 03170                    targetAr);
 3171            }
 3172
 03173            return string.Empty;
 3174        }
 3175
 3176        private static string GetFixedSwScaleFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight
 3177        {
 03178            var widthParam = requestedWidth.ToString(CultureInfo.InvariantCulture);
 03179            var heightParam = requestedHeight.ToString(CultureInfo.InvariantCulture);
 3180
 03181            string filter = null;
 3182
 03183            if (threedFormat.HasValue)
 3184            {
 03185                switch (threedFormat.Value)
 3186                {
 3187                    case Video3DFormat.HalfSideBySide:
 03188                        filter = @"crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(i
 3189                        // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars
 03190                        break;
 3191                    case Video3DFormat.FullSideBySide:
 03192                        filter = @"crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3193                        // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the s
 03194                        break;
 3195                    case Video3DFormat.HalfTopAndBottom:
 03196                        filter = @"crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(
 3197                        // htab crop height in half,scale to correct size, set the display aspect,crop out any black bar
 03198                        break;
 3199                    case Video3DFormat.FullTopAndBottom:
 03200                        filter = @"crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3201                        // ftab crop height in half, set the display aspect,crop out any black bars we may have made the
 3202                        break;
 3203                    default:
 3204                        break;
 3205                }
 3206            }
 3207
 3208            // default
 03209            if (filter is null)
 3210            {
 03211                if (requestedHeight > 0)
 3212                {
 03213                    filter = "scale=trunc({0}/2)*2:trunc({1}/2)*2";
 3214                }
 3215                else
 3216                {
 03217                    filter = "scale={0}:trunc({0}/a/2)*2";
 3218                }
 3219            }
 3220
 03221            return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam);
 3222        }
 3223
 3224        public static string GetSwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options)
 3225        {
 03226            var doubleRateDeint = options.DeinterlaceDoubleRate && state.VideoStream?.ReferenceFrameRate <= 30;
 03227            return string.Format(
 03228                CultureInfo.InvariantCulture,
 03229                "{0}={1}:-1:0",
 03230                options.DeinterlaceMethod.ToString().ToLowerInvariant(),
 03231                doubleRateDeint ? "1" : "0");
 3232        }
 3233
 3234        public string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix)
 3235        {
 03236            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03237            if (hwDeintSuffix.Contains("cuda", StringComparison.OrdinalIgnoreCase))
 3238            {
 03239                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3240
 03241                return string.Format(
 03242                    CultureInfo.InvariantCulture,
 03243                    "{0}_cuda={1}:-1:0",
 03244                    useBwdif ? "bwdif" : "yadif",
 03245                    doubleRateDeint ? "1" : "0");
 3246            }
 3247
 03248            if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
 3249            {
 03250                return string.Format(
 03251                    CultureInfo.InvariantCulture,
 03252                    "deinterlace_vaapi=rate={0}",
 03253                    doubleRateDeint ? "field" : "frame");
 3254            }
 3255
 03256            if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
 3257            {
 03258                return "deinterlace_qsv=mode=2";
 3259            }
 3260
 03261            if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
 3262            {
 03263                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3264
 03265                return string.Format(
 03266                    CultureInfo.InvariantCulture,
 03267                    "{0}_videotoolbox={1}:-1:0",
 03268                    useBwdif ? "bwdif" : "yadif",
 03269                    doubleRateDeint ? "1" : "0");
 3270            }
 3271
 03272            return string.Empty;
 3273        }
 3274
 3275        public string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat)
 3276        {
 03277            if (string.IsNullOrEmpty(hwTonemapSuffix))
 3278            {
 03279                return string.Empty;
 3280            }
 3281
 03282            var args = string.Empty;
 03283            var algorithm = options.TonemappingAlgorithm.ToString().ToLowerInvariant();
 03284            var mode = options.TonemappingMode.ToString().ToLowerInvariant();
 03285            var range = options.TonemappingRange;
 03286            var rangeString = range.ToString().ToLowerInvariant();
 3287
 03288            if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
 3289            {
 03290                var doVaVppProcamp = false;
 03291                var procampParams = string.Empty;
 03292                if (options.VppTonemappingBrightness != 0
 03293                    && options.VppTonemappingBrightness >= -100
 03294                    && options.VppTonemappingBrightness <= 100)
 3295                {
 03296                    procampParams += $"=b={options.VppTonemappingBrightness}";
 03297                    doVaVppProcamp = true;
 3298                }
 3299
 03300                if (options.VppTonemappingContrast > 1
 03301                    && options.VppTonemappingContrast <= 10)
 3302                {
 03303                    procampParams += doVaVppProcamp ? ":" : "=";
 03304                    procampParams += $"c={options.VppTonemappingContrast}";
 03305                    doVaVppProcamp = true;
 3306                }
 3307
 03308                args = "{0}tonemap_vaapi=format={1}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
 3309
 03310                return string.Format(
 03311                        CultureInfo.InvariantCulture,
 03312                        args,
 03313                        doVaVppProcamp ? $"procamp_vaapi{procampParams}," : string.Empty,
 03314                        videoFormat ?? "nv12");
 3315            }
 3316            else
 3317            {
 03318                args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
 3319
 03320                var useLegacyTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode
 03321                                           && _legacyTonemapModes.Contains(options.TonemappingMode);
 3322
 03323                var useAdvancedTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegAdvancedTonemapMode
 03324                                              && _advancedTonemapModes.Contains(options.TonemappingMode);
 3325
 03326                if (useLegacyTonemapModes || useAdvancedTonemapModes)
 3327                {
 03328                    args += ":tonemap_mode={5}";
 3329                }
 3330
 03331                if (options.TonemappingParam != 0)
 3332                {
 03333                    args += ":param={6}";
 3334                }
 3335
 03336                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3337                {
 03338                    args += ":range={7}";
 3339                }
 3340            }
 3341
 03342            return string.Format(
 03343                    CultureInfo.InvariantCulture,
 03344                    args,
 03345                    hwTonemapSuffix,
 03346                    videoFormat ?? "nv12",
 03347                    algorithm,
 03348                    options.TonemappingPeak,
 03349                    options.TonemappingDesat,
 03350                    mode,
 03351                    options.TonemappingParam,
 03352                    rangeString);
 3353        }
 3354
 3355        public string GetLibplaceboFilter(
 3356            EncodingOptions options,
 3357            string videoFormat,
 3358            bool doTonemap,
 3359            int? videoWidth,
 3360            int? videoHeight,
 3361            int? requestedWidth,
 3362            int? requestedHeight,
 3363            int? requestedMaxWidth,
 3364            int? requestedMaxHeight)
 3365        {
 03366            var (outWidth, outHeight) = GetFixedOutputSize(
 03367                videoWidth,
 03368                videoHeight,
 03369                requestedWidth,
 03370                requestedHeight,
 03371                requestedMaxWidth,
 03372                requestedMaxHeight);
 3373
 03374            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03375            var isSizeFixed = !videoWidth.HasValue
 03376                || outWidth.Value != videoWidth.Value
 03377                || !videoHeight.HasValue
 03378                || outHeight.Value != videoHeight.Value;
 3379
 03380            var sizeArg = isSizeFixed ? (":w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty;
 03381            var formatArg = isFormatFixed ? (":format=" + videoFormat) : string.Empty;
 03382            var tonemapArg = string.Empty;
 3383
 03384            if (doTonemap)
 3385            {
 03386                var algorithm = options.TonemappingAlgorithm;
 3387                var algorithmString = "clip";
 03388                var mode = options.TonemappingMode;
 03389                var range = options.TonemappingRange;
 3390
 03391                if (algorithm == TonemappingAlgorithm.bt2390)
 3392                {
 3393                    algorithmString = "bt.2390";
 3394                }
 03395                else if (algorithm != TonemappingAlgorithm.none)
 3396                {
 03397                    algorithmString = algorithm.ToString().ToLowerInvariant();
 3398                }
 3399
 03400                tonemapArg = ":tonemapping=" + algorithm + ":peak_detect=0:color_primaries=bt709:color_trc=bt709:colorsp
 3401
 03402                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3403                {
 03404                    tonemapArg += ":range=" + range.ToString().ToLowerInvariant();
 3405                }
 3406            }
 3407
 03408            return string.Format(
 03409                CultureInfo.InvariantCulture,
 03410                "libplacebo=upscaler=none:downscaler=none{0}{1}{2}",
 03411                sizeArg,
 03412                formatArg,
 03413                tonemapArg);
 3414        }
 3415
 3416        public string GetVideoTransposeDirection(EncodingJobInfo state)
 3417        {
 03418            return (state.VideoStream?.Rotation ?? 0) switch
 03419            {
 03420                90 => "cclock",
 03421                180 => "reversal",
 03422                -90 => "clock",
 03423                -180 => "reversal",
 03424                _ => string.Empty
 03425            };
 3426        }
 3427
 3428        /// <summary>
 3429        /// Gets the parameter of software filter chain.
 3430        /// </summary>
 3431        /// <param name="state">Encoding state.</param>
 3432        /// <param name="options">Encoding options.</param>
 3433        /// <param name="vidEncoder">Video encoder to use.</param>
 3434        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3435        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetSwVidFilterChain(
 3436            EncodingJobInfo state,
 3437            EncodingOptions options,
 3438            string vidEncoder)
 3439        {
 03440            var inW = state.VideoStream?.Width;
 03441            var inH = state.VideoStream?.Height;
 03442            var reqW = state.BaseRequest.Width;
 03443            var reqH = state.BaseRequest.Height;
 03444            var reqMaxW = state.BaseRequest.MaxWidth;
 03445            var reqMaxH = state.BaseRequest.MaxHeight;
 03446            var threeDFormat = state.MediaSource.Video3DFormat;
 3447
 03448            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03449            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03450            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 03451            var isV4l2Encoder = vidEncoder.Contains("h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 3452
 03453            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03454            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03455            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03456            var doToneMap = IsSwTonemapAvailable(state, options);
 03457            var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI;
 3458
 03459            var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Enc
 03460            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03461            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 3462
 03463            var rotation = state.VideoStream?.Rotation ?? 0;
 03464            var swapWAndH = Math.Abs(rotation) == 90;
 03465            var swpInW = swapWAndH ? inH : inW;
 03466            var swpInH = swapWAndH ? inW : inH;
 3467
 3468            /* Make main filters for video stream */
 03469            var mainFilters = new List<string>();
 3470
 03471            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doToneMap));
 3472
 3473            // INPUT sw surface(memory/copy-back from vram)
 3474            // sw deint
 03475            if (doDeintH2645)
 3476            {
 03477                var deintFilter = GetSwDeinterlaceFilter(state, options);
 03478                mainFilters.Add(deintFilter);
 3479            }
 3480
 03481            var outFormat = isSwDecoder ? "yuv420p" : "nv12";
 03482            var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, r
 03483            if (isVaapiEncoder)
 3484            {
 03485                outFormat = "nv12";
 3486            }
 03487            else if (isV4l2Encoder)
 3488            {
 03489                outFormat = "yuv420p";
 3490            }
 3491
 3492            // sw scale
 03493            mainFilters.Add(swScaleFilter);
 3494
 3495            // sw tonemap
 03496            if (doToneMap)
 3497            {
 3498                // tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary
 03499                var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat;
 3500
 03501                var tonemapArgs = $"tonemapx=tonemap={options.TonemappingAlgorithm}:desat={options.TonemappingDesat}:pea
 3502
 03503                if (options.TonemappingParam != 0)
 3504                {
 03505                    tonemapArgs += $":param={options.TonemappingParam}";
 3506                }
 3507
 03508                var range = options.TonemappingRange;
 03509                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3510                {
 03511                    tonemapArgs += $":range={options.TonemappingRange}";
 3512                }
 3513
 03514                mainFilters.Add(tonemapArgs);
 3515            }
 3516            else
 3517            {
 3518                // OUTPUT yuv420p/nv12 surface(memory)
 03519                mainFilters.Add("format=" + outFormat);
 3520            }
 3521
 3522            /* Make sub and overlay filters for subtitle stream */
 03523            var subFilters = new List<string>();
 03524            var overlayFilters = new List<string>();
 03525            if (hasTextSubs)
 3526            {
 3527                // subtitles=f='*.ass':alpha=0
 03528                var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03529                mainFilters.Add(textSubtitlesFilter);
 3530            }
 03531            else if (hasGraphicalSubs)
 3532            {
 03533                var subW = state.SubtitleStream?.Width;
 03534                var subH = state.SubtitleStream?.Height;
 03535                var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW
 03536                subFilters.Add(subPreProcFilters);
 03537                overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 3538            }
 3539
 03540            return (mainFilters, subFilters, overlayFilters);
 3541        }
 3542
 3543        /// <summary>
 3544        /// Gets the parameter of Nvidia NVENC filter chain.
 3545        /// </summary>
 3546        /// <param name="state">Encoding state.</param>
 3547        /// <param name="options">Encoding options.</param>
 3548        /// <param name="vidEncoder">Video encoder to use.</param>
 3549        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3550        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFilterChain(
 3551            EncodingJobInfo state,
 3552            EncodingOptions options,
 3553            string vidEncoder)
 3554        {
 03555            if (options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 3556            {
 03557                return (null, null, null);
 3558            }
 3559
 03560            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03561            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03562            var isSwEncoder = !vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 3563
 3564            // legacy cuvid pipeline(copy-back)
 03565            if ((isSwDecoder && isSwEncoder)
 03566                || !IsCudaFullSupported()
 03567                || !_mediaEncoder.SupportsFilter("alphasrc"))
 3568            {
 03569                return GetSwVidFilterChain(state, options, vidEncoder);
 3570            }
 3571
 3572            // prefered nvdec/cuvid + cuda filters + nvenc pipeline
 03573            return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 3574        }
 3575
 3576        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFiltersPrefe
 3577            EncodingJobInfo state,
 3578            EncodingOptions options,
 3579            string vidDecoder,
 3580            string vidEncoder)
 3581        {
 03582            var inW = state.VideoStream?.Width;
 03583            var inH = state.VideoStream?.Height;
 03584            var reqW = state.BaseRequest.Width;
 03585            var reqH = state.BaseRequest.Height;
 03586            var reqMaxW = state.BaseRequest.MaxWidth;
 03587            var reqMaxH = state.BaseRequest.MaxHeight;
 03588            var threeDFormat = state.MediaSource.Video3DFormat;
 3589
 03590            var isNvDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 03591            var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 03592            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03593            var isSwEncoder = !isNvencEncoder;
 03594            var isCuInCuOut = isNvDecoder && isNvencEncoder;
 3595
 03596            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03597            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03598            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03599            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03600            var doCuTonemap = IsHwTonemapAvailable(state, options);
 3601
 03602            var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Enc
 03603            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03604            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 03605            var hasAssSubs = hasSubs
 03606                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 03607                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 03608            var subW = state.SubtitleStream?.Width;
 03609            var subH = state.SubtitleStream?.Height;
 3610
 03611            var rotation = state.VideoStream?.Rotation ?? 0;
 03612            var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 03613            var doCuTranspose = !string.IsNullOrEmpty(tranposeDir) && _mediaEncoder.SupportsFilter("transpose_cuda");
 03614            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isNvDecoder && doCuTranspose));
 03615            var swpInW = swapWAndH ? inH : inW;
 03616            var swpInH = swapWAndH ? inW : inH;
 3617
 3618            /* Make main filters for video stream */
 03619            var mainFilters = new List<string>();
 3620
 03621            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doCuTonemap));
 3622
 03623            if (isSwDecoder)
 3624            {
 3625                // INPUT sw surface(memory)
 3626                // sw deint
 03627                if (doDeintH2645)
 3628                {
 03629                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 03630                    mainFilters.Add(swDeintFilter);
 3631                }
 3632
 03633                var outFormat = doCuTonemap ? "yuv420p10le" : "yuv420p";
 03634                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 3635                // sw scale
 03636                mainFilters.Add(swScaleFilter);
 03637                mainFilters.Add($"format={outFormat}");
 3638
 3639                // sw => hw
 03640                if (doCuTonemap)
 3641                {
 03642                    mainFilters.Add("hwupload=derive_device=cuda");
 3643                }
 3644            }
 3645
 03646            if (isNvDecoder)
 3647            {
 3648                // INPUT cuda surface(vram)
 3649                // hw deint
 03650                if (doDeintH2645)
 3651                {
 03652                    var deintFilter = GetHwDeinterlaceFilter(state, options, "cuda");
 03653                    mainFilters.Add(deintFilter);
 3654                }
 3655
 3656                // hw transpose
 03657                if (doCuTranspose)
 3658                {
 03659                    mainFilters.Add($"transpose_cuda=dir={tranposeDir}");
 3660                }
 3661
 03662                var outFormat = doCuTonemap ? string.Empty : "yuv420p";
 03663                var hwScaleFilter = GetHwScaleFilter("scale", "cuda", outFormat, false, swpInW, swpInH, reqW, reqH, reqM
 3664                // hw scale
 03665                mainFilters.Add(hwScaleFilter);
 3666            }
 3667
 3668            // hw tonemap
 03669            if (doCuTonemap)
 3670            {
 03671                var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p");
 03672                mainFilters.Add(tonemapFilter);
 3673            }
 3674
 03675            var memoryOutput = false;
 03676            var isUploadForCuTonemap = isSwDecoder && doCuTonemap;
 03677            if ((isNvDecoder && isSwEncoder) || (isUploadForCuTonemap && hasSubs))
 3678            {
 03679                memoryOutput = true;
 3680
 3681                // OUTPUT yuv420p surface(memory)
 03682                mainFilters.Add("hwdownload");
 03683                mainFilters.Add("format=yuv420p");
 3684            }
 3685
 3686            // OUTPUT yuv420p surface(memory)
 03687            if (isSwDecoder && isNvencEncoder && !isUploadForCuTonemap)
 3688            {
 03689                memoryOutput = true;
 3690            }
 3691
 03692            if (memoryOutput)
 3693            {
 3694                // text subtitles
 03695                if (hasTextSubs)
 3696                {
 03697                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03698                    mainFilters.Add(textSubtitlesFilter);
 3699                }
 3700            }
 3701
 3702            // OUTPUT cuda(yuv420p) surface(vram)
 3703
 3704            /* Make sub and overlay filters for subtitle stream */
 03705            var subFilters = new List<string>();
 03706            var overlayFilters = new List<string>();
 03707            if (isCuInCuOut)
 3708            {
 03709                if (hasSubs)
 3710                {
 03711                    if (hasGraphicalSubs)
 3712                    {
 03713                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 03714                        subFilters.Add(subPreProcFilters);
 03715                        subFilters.Add("format=yuva420p");
 3716                    }
 03717                    else if (hasTextSubs)
 3718                    {
 03719                        var framerate = state.VideoStream?.RealFrameRate;
 03720                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 3721
 3722                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 03723                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 03724                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 03725                        subFilters.Add(alphaSrcFilter);
 03726                        subFilters.Add("format=yuva420p");
 03727                        subFilters.Add(subTextSubtitlesFilter);
 3728                    }
 3729
 03730                    subFilters.Add("hwupload=derive_device=cuda");
 03731                    overlayFilters.Add("overlay_cuda=eof_action=pass:repeatlast=0");
 3732                }
 3733            }
 3734            else
 3735            {
 03736                if (hasGraphicalSubs)
 3737                {
 03738                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 03739                    subFilters.Add(subPreProcFilters);
 03740                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 3741                }
 3742            }
 3743
 03744            return (mainFilters, subFilters, overlayFilters);
 3745        }
 3746
 3747        /// <summary>
 3748        /// Gets the parameter of AMD AMF filter chain.
 3749        /// </summary>
 3750        /// <param name="state">Encoding state.</param>
 3751        /// <param name="options">Encoding options.</param>
 3752        /// <param name="vidEncoder">Video encoder to use.</param>
 3753        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3754        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVidFilterChain(
 3755            EncodingJobInfo state,
 3756            EncodingOptions options,
 3757            string vidEncoder)
 3758        {
 03759            if (options.HardwareAccelerationType != HardwareAccelerationType.amf)
 3760            {
 03761                return (null, null, null);
 3762            }
 3763
 03764            var isWindows = OperatingSystem.IsWindows();
 03765            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03766            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03767            var isSwEncoder = !vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 03768            var isAmfDx11OclSupported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va") && IsOpenclFullSupported()
 3769
 3770            // legacy d3d11va pipeline(copy-back)
 03771            if ((isSwDecoder && isSwEncoder)
 03772                || !isAmfDx11OclSupported
 03773                || !_mediaEncoder.SupportsFilter("alphasrc"))
 3774            {
 03775                return GetSwVidFilterChain(state, options, vidEncoder);
 3776            }
 3777
 3778            // prefered d3d11va + opencl filters + amf pipeline
 03779            return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 3780        }
 3781
 3782        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdDx11VidFiltersPref
 3783            EncodingJobInfo state,
 3784            EncodingOptions options,
 3785            string vidDecoder,
 3786            string vidEncoder)
 3787        {
 03788            var inW = state.VideoStream?.Width;
 03789            var inH = state.VideoStream?.Height;
 03790            var reqW = state.BaseRequest.Width;
 03791            var reqH = state.BaseRequest.Height;
 03792            var reqMaxW = state.BaseRequest.MaxWidth;
 03793            var reqMaxH = state.BaseRequest.MaxHeight;
 03794            var threeDFormat = state.MediaSource.Video3DFormat;
 3795
 03796            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 03797            var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 03798            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03799            var isSwEncoder = !isAmfEncoder;
 03800            var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder;
 3801
 03802            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03803            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03804            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03805            var doOclTonemap = IsHwTonemapAvailable(state, options);
 3806
 03807            var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Enc
 03808            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03809            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 03810            var hasAssSubs = hasSubs
 03811                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 03812                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 03813            var subW = state.SubtitleStream?.Width;
 03814            var subH = state.SubtitleStream?.Height;
 3815
 03816            var rotation = state.VideoStream?.Rotation ?? 0;
 03817            var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 03818            var doOclTranspose = !string.IsNullOrEmpty(tranposeDir)
 03819                && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TransposeOpenclReversal);
 03820            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isD3d11vaDecoder && doOclTranspose));
 03821            var swpInW = swapWAndH ? inH : inW;
 03822            var swpInH = swapWAndH ? inW : inH;
 3823
 3824            /* Make main filters for video stream */
 03825            var mainFilters = new List<string>();
 3826
 03827            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 3828
 03829            if (isSwDecoder)
 3830            {
 3831                // INPUT sw surface(memory)
 3832                // sw deint
 03833                if (doDeintH2645)
 3834                {
 03835                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 03836                    mainFilters.Add(swDeintFilter);
 3837                }
 3838
 03839                var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
 03840                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 3841                // sw scale
 03842                mainFilters.Add(swScaleFilter);
 03843                mainFilters.Add($"format={outFormat}");
 3844
 3845                // keep video at memory except ocl tonemap,
 3846                // since the overhead caused by hwupload >>> using sw filter.
 3847                // sw => hw
 03848                if (doOclTonemap)
 3849                {
 03850                    mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=24");
 03851                    mainFilters.Add("format=d3d11");
 03852                    mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 3853                }
 3854            }
 3855
 03856            if (isD3d11vaDecoder)
 3857            {
 3858                // INPUT d3d11 surface(vram)
 3859                // map from d3d11va to opencl via d3d11-opencl interop.
 03860                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 3861
 3862                // hw deint <= TODO: finsh the 'yadif_opencl' filter
 3863
 3864                // hw transpose
 03865                if (doOclTranspose)
 3866                {
 03867                    mainFilters.Add($"transpose_opencl=dir={tranposeDir}");
 3868                }
 3869
 03870                var outFormat = doOclTonemap ? string.Empty : "nv12";
 03871                var hwScaleFilter = GetHwScaleFilter("scale", "opencl", outFormat, false, swpInW, swpInH, reqW, reqH, re
 3872                // hw scale
 03873                mainFilters.Add(hwScaleFilter);
 3874            }
 3875
 3876            // hw tonemap
 03877            if (doOclTonemap)
 3878            {
 03879                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
 03880                mainFilters.Add(tonemapFilter);
 3881            }
 3882
 03883            var memoryOutput = false;
 03884            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 03885            if (isD3d11vaDecoder && isSwEncoder)
 3886            {
 03887                memoryOutput = true;
 3888
 3889                // OUTPUT nv12 surface(memory)
 3890                // prefer hwmap to hwdownload on opencl.
 03891                var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap=mode=read";
 03892                mainFilters.Add(hwTransferFilter);
 03893                mainFilters.Add("format=nv12");
 3894            }
 3895
 3896            // OUTPUT yuv420p surface
 03897            if (isSwDecoder && isAmfEncoder && !isUploadForOclTonemap)
 3898            {
 03899                memoryOutput = true;
 3900            }
 3901
 03902            if (memoryOutput)
 3903            {
 3904                // text subtitles
 03905                if (hasTextSubs)
 3906                {
 03907                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03908                    mainFilters.Add(textSubtitlesFilter);
 3909                }
 3910            }
 3911
 03912            if ((isDxInDxOut || isUploadForOclTonemap) && !hasSubs)
 3913            {
 3914                // OUTPUT d3d11(nv12) surface(vram)
 3915                // reverse-mapping via d3d11-opencl interop.
 03916                mainFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 03917                mainFilters.Add("format=d3d11");
 3918            }
 3919
 3920            /* Make sub and overlay filters for subtitle stream */
 03921            var subFilters = new List<string>();
 03922            var overlayFilters = new List<string>();
 03923            if (isDxInDxOut || isUploadForOclTonemap)
 3924            {
 03925                if (hasSubs)
 3926                {
 03927                    if (hasGraphicalSubs)
 3928                    {
 03929                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 03930                        subFilters.Add(subPreProcFilters);
 03931                        subFilters.Add("format=yuva420p");
 3932                    }
 03933                    else if (hasTextSubs)
 3934                    {
 03935                        var framerate = state.VideoStream?.RealFrameRate;
 03936                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 3937
 3938                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 03939                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 03940                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 03941                        subFilters.Add(alphaSrcFilter);
 03942                        subFilters.Add("format=yuva420p");
 03943                        subFilters.Add(subTextSubtitlesFilter);
 3944                    }
 3945
 03946                    subFilters.Add("hwupload=derive_device=opencl");
 03947                    overlayFilters.Add("overlay_opencl=eof_action=pass:repeatlast=0");
 03948                    overlayFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 03949                    overlayFilters.Add("format=d3d11");
 3950                }
 3951            }
 03952            else if (memoryOutput)
 3953            {
 03954                if (hasGraphicalSubs)
 3955                {
 03956                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 03957                    subFilters.Add(subPreProcFilters);
 03958                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 3959                }
 3960            }
 3961
 03962            return (mainFilters, subFilters, overlayFilters);
 3963        }
 3964
 3965        /// <summary>
 3966        /// Gets the parameter of Intel QSV filter chain.
 3967        /// </summary>
 3968        /// <param name="state">Encoding state.</param>
 3969        /// <param name="options">Encoding options.</param>
 3970        /// <param name="vidEncoder">Video encoder to use.</param>
 3971        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3972        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVidFilterChain(
 3973            EncodingJobInfo state,
 3974            EncodingOptions options,
 3975            string vidEncoder)
 3976        {
 03977            if (options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 3978            {
 03979                return (null, null, null);
 3980            }
 3981
 03982            var isWindows = OperatingSystem.IsWindows();
 03983            var isLinux = OperatingSystem.IsLinux();
 03984            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03985            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03986            var isSwEncoder = !vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 03987            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 03988            var isIntelDx11OclSupported = isWindows
 03989                && _mediaEncoder.SupportsHwaccel("d3d11va")
 03990                && isQsvOclSupported;
 03991            var isIntelVaapiOclSupported = isLinux
 03992                && IsVaapiSupported(state)
 03993                && isQsvOclSupported;
 3994
 3995            // legacy qsv pipeline(copy-back)
 03996            if ((isSwDecoder && isSwEncoder)
 03997                || (!isIntelVaapiOclSupported && !isIntelDx11OclSupported)
 03998                || !_mediaEncoder.SupportsFilter("alphasrc"))
 3999            {
 04000                return GetSwVidFilterChain(state, options, vidEncoder);
 4001            }
 4002
 4003            // prefered qsv(vaapi) + opencl filters pipeline
 04004            if (isIntelVaapiOclSupported)
 4005            {
 04006                return GetIntelQsvVaapiVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4007            }
 4008
 4009            // prefered qsv(d3d11) + opencl filters pipeline
 04010            if (isIntelDx11OclSupported)
 4011            {
 04012                return GetIntelQsvDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4013            }
 4014
 04015            return (null, null, null);
 4016        }
 4017
 4018        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvDx11VidFilter
 4019            EncodingJobInfo state,
 4020            EncodingOptions options,
 4021            string vidDecoder,
 4022            string vidEncoder)
 4023        {
 04024            var inW = state.VideoStream?.Width;
 04025            var inH = state.VideoStream?.Height;
 04026            var reqW = state.BaseRequest.Width;
 04027            var reqH = state.BaseRequest.Height;
 04028            var reqMaxW = state.BaseRequest.MaxWidth;
 04029            var reqMaxH = state.BaseRequest.MaxHeight;
 04030            var threeDFormat = state.MediaSource.Video3DFormat;
 4031
 04032            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04033            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04034            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04035            var isHwDecoder = isD3d11vaDecoder || isQsvDecoder;
 04036            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04037            var isSwEncoder = !isQsvEncoder;
 04038            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4039
 04040            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04041            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04042            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04043            var doVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04044            var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options);
 04045            var doTonemap = doVppTonemap || doOclTonemap;
 4046
 04047            var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Enc
 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 tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04058            var doVppTranspose = !string.IsNullOrEmpty(tranposeDir);
 04059            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isD3d11vaDecoder || isQsvDecoder) && doVppTran
 04060            var swpInW = swapWAndH ? inH : inW;
 04061            var swpInH = swapWAndH ? inW : inH;
 4062
 4063            /* Make main filters for video stream */
 04064            var mainFilters = new List<string>();
 4065
 04066            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4067
 04068            if (isSwDecoder)
 4069            {
 4070                // INPUT sw surface(memory)
 4071                // sw deint
 04072                if (doDeintH2645)
 4073                {
 04074                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04075                    mainFilters.Add(swDeintFilter);
 4076                }
 4077
 04078                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04079                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 4080                // sw scale
 04081                mainFilters.Add(swScaleFilter);
 04082                mainFilters.Add($"format={outFormat}");
 4083
 4084                // keep video at memory except ocl tonemap,
 4085                // since the overhead caused by hwupload >>> using sw filter.
 4086                // sw => hw
 04087                if (doOclTonemap)
 4088                {
 04089                    mainFilters.Add("hwupload=derive_device=opencl");
 4090                }
 4091            }
 04092            else if (isD3d11vaDecoder || isQsvDecoder)
 4093            {
 04094                var doVppProcamp = false;
 04095                var procampParams = string.Empty;
 04096                if (doVppTonemap)
 4097                {
 04098                    if (options.VppTonemappingBrightness != 0
 04099                        && options.VppTonemappingBrightness >= -100
 04100                        && options.VppTonemappingBrightness <= 100)
 4101                    {
 04102                        procampParams += $":brightness={options.VppTonemappingBrightness}";
 04103                        doVppProcamp = true;
 4104                    }
 4105
 04106                    if (options.VppTonemappingContrast > 1
 04107                        && options.VppTonemappingContrast <= 10)
 4108                    {
 04109                        procampParams += $":contrast={options.VppTonemappingContrast}";
 04110                        doVppProcamp = true;
 4111                    }
 4112
 04113                    procampParams += doVppProcamp ? ":procamp=1:async_depth=2" : string.Empty;
 4114                }
 4115
 04116                var outFormat = doOclTonemap ? (doVppTranspose ? "p010" : string.Empty) : "nv12";
 04117                outFormat = (doVppTonemap && doVppProcamp) ? "p010" : outFormat;
 4118
 04119                var swapOutputWandH = doVppTranspose && swapWAndH;
 04120                var hwScalePrefix = (doVppTranspose || doVppTonemap) ? "vpp" : "scale";
 04121                var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, "qsv", outFormat, swapOutputWandH, swpInW, swpInH, r
 4122
 04123                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose)
 4124                {
 04125                    hwScaleFilter += $":transpose={tranposeDir}";
 4126                }
 4127
 04128                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTonemap)
 4129                {
 04130                    hwScaleFilter += doVppProcamp ? procampParams : ":tonemap=1";
 4131                }
 4132
 04133                if (isD3d11vaDecoder)
 4134                {
 04135                    if (!string.IsNullOrEmpty(hwScaleFilter) || doDeintH2645)
 4136                    {
 4137                        // INPUT d3d11 surface(vram)
 4138                        // map from d3d11va to qsv.
 04139                        mainFilters.Add("hwmap=derive_device=qsv");
 4140                    }
 4141                }
 4142
 4143                // hw deint
 04144                if (doDeintH2645)
 4145                {
 04146                    var deintFilter = GetHwDeinterlaceFilter(state, options, "qsv");
 04147                    mainFilters.Add(deintFilter);
 4148                }
 4149
 4150                // hw transpose & scale & tonemap(w/o procamp)
 04151                mainFilters.Add(hwScaleFilter);
 4152
 4153                // hw tonemap(w/ procamp)
 04154                if (doVppTonemap && doVppProcamp)
 4155                {
 04156                    mainFilters.Add("vpp_qsv=tonemap=1:format=nv12:async_depth=2");
 4157                }
 4158
 4159                // force bt709 just in case vpp tonemap is not triggered or using MSDK instead of VPL.
 04160                if (doVppTonemap)
 4161                {
 04162                    mainFilters.Add(GetOverwriteColorPropertiesParam(state, false));
 4163                }
 4164            }
 4165
 04166            if (doOclTonemap && isHwDecoder)
 4167            {
 4168                // map from qsv to opencl via qsv(d3d11)-opencl interop.
 04169                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4170            }
 4171
 4172            // hw tonemap
 04173            if (doOclTonemap)
 4174            {
 04175                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
 04176                mainFilters.Add(tonemapFilter);
 4177            }
 4178
 04179            var memoryOutput = false;
 04180            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04181            var isHwmapUsable = isSwEncoder && doOclTonemap;
 04182            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4183            {
 04184                memoryOutput = true;
 4185
 4186                // OUTPUT nv12 surface(memory)
 4187                // prefer hwmap to hwdownload on opencl.
 4188                // qsv hwmap is not fully implemented for the time being.
 04189                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04190                mainFilters.Add("format=nv12");
 4191            }
 4192
 4193            // OUTPUT nv12 surface(memory)
 04194            if (isSwDecoder && isQsvEncoder)
 4195            {
 04196                memoryOutput = true;
 4197            }
 4198
 04199            if (memoryOutput)
 4200            {
 4201                // text subtitles
 04202                if (hasTextSubs)
 4203                {
 04204                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04205                    mainFilters.Add(textSubtitlesFilter);
 4206                }
 4207            }
 4208
 04209            if (isQsvInQsvOut && doOclTonemap)
 4210            {
 4211                // OUTPUT qsv(nv12) surface(vram)
 4212                // reverse-mapping via qsv(d3d11)-opencl interop.
 04213                mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1");
 04214                mainFilters.Add("format=qsv");
 4215            }
 4216
 4217            /* Make sub and overlay filters for subtitle stream */
 04218            var subFilters = new List<string>();
 04219            var overlayFilters = new List<string>();
 04220            if (isQsvInQsvOut)
 4221            {
 04222                if (hasSubs)
 4223                {
 04224                    if (hasGraphicalSubs)
 4225                    {
 4226                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04227                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04228                        subFilters.Add(subPreProcFilters);
 04229                        subFilters.Add("format=bgra");
 4230                    }
 04231                    else if (hasTextSubs)
 4232                    {
 04233                        var framerate = state.VideoStream?.RealFrameRate;
 04234                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4235
 4236                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 04237                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04238                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04239                        subFilters.Add(alphaSrcFilter);
 04240                        subFilters.Add("format=bgra");
 04241                        subFilters.Add(subTextSubtitlesFilter);
 4242                    }
 4243
 4244                    // qsv requires a fixed pool size.
 4245                    // default to 64 otherwise it will fail on certain iGPU.
 04246                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4247
 04248                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04249                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04250                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04251                        : string.Empty;
 04252                    var overlayQsvFilter = string.Format(
 04253                        CultureInfo.InvariantCulture,
 04254                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04255                        overlaySize);
 04256                    overlayFilters.Add(overlayQsvFilter);
 4257                }
 4258            }
 04259            else if (memoryOutput)
 4260            {
 04261                if (hasGraphicalSubs)
 4262                {
 04263                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04264                    subFilters.Add(subPreProcFilters);
 04265                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4266                }
 4267            }
 4268
 04269            return (mainFilters, subFilters, overlayFilters);
 4270        }
 4271
 4272        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvVaapiVidFilte
 4273            EncodingJobInfo state,
 4274            EncodingOptions options,
 4275            string vidDecoder,
 4276            string vidEncoder)
 4277        {
 04278            var inW = state.VideoStream?.Width;
 04279            var inH = state.VideoStream?.Height;
 04280            var reqW = state.BaseRequest.Width;
 04281            var reqH = state.BaseRequest.Height;
 04282            var reqMaxW = state.BaseRequest.MaxWidth;
 04283            var reqMaxH = state.BaseRequest.MaxHeight;
 04284            var threeDFormat = state.MediaSource.Video3DFormat;
 4285
 04286            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04287            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04288            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04289            var isHwDecoder = isVaapiDecoder || isQsvDecoder;
 04290            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04291            var isSwEncoder = !isQsvEncoder;
 04292            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4293
 04294            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04295            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04296            var doVaVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04297            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04298            var doTonemap = doVaVppTonemap || doOclTonemap;
 04299            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4300
 04301            var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Enc
 04302            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04303            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04304            var hasAssSubs = hasSubs
 04305                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04306                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04307            var subW = state.SubtitleStream?.Width;
 04308            var subH = state.SubtitleStream?.Height;
 4309
 04310            var rotation = state.VideoStream?.Rotation ?? 0;
 04311            var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04312            var doVppTranspose = !string.IsNullOrEmpty(tranposeDir);
 04313            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isVaapiDecoder || isQsvDecoder) && doVppTransp
 04314            var swpInW = swapWAndH ? inH : inW;
 04315            var swpInH = swapWAndH ? inW : inH;
 4316
 4317            /* Make main filters for video stream */
 04318            var mainFilters = new List<string>();
 4319
 04320            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4321
 04322            if (isSwDecoder)
 4323            {
 4324                // INPUT sw surface(memory)
 4325                // sw deint
 04326                if (doDeintH2645)
 4327                {
 04328                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04329                    mainFilters.Add(swDeintFilter);
 4330                }
 4331
 04332                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04333                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 4334                // sw scale
 04335                mainFilters.Add(swScaleFilter);
 04336                mainFilters.Add($"format={outFormat}");
 4337
 4338                // keep video at memory except ocl tonemap,
 4339                // since the overhead caused by hwupload >>> using sw filter.
 4340                // sw => hw
 04341                if (doOclTonemap)
 4342                {
 04343                    mainFilters.Add("hwupload=derive_device=opencl");
 4344                }
 4345            }
 04346            else if (isVaapiDecoder || isQsvDecoder)
 4347            {
 04348                var hwFilterSuffix = isVaapiDecoder ? "vaapi" : "qsv";
 4349
 4350                // INPUT vaapi/qsv surface(vram)
 4351                // hw deint
 04352                if (doDeintH2645)
 4353                {
 04354                    var deintFilter = GetHwDeinterlaceFilter(state, options, hwFilterSuffix);
 04355                    mainFilters.Add(deintFilter);
 4356                }
 4357
 4358                // hw transpose(vaapi vpp)
 04359                if (isVaapiDecoder && doVppTranspose)
 4360                {
 04361                    mainFilters.Add($"transpose_vaapi=dir={tranposeDir}");
 4362                }
 4363
 04364                var outFormat = doOclTonemap ? ((isQsvDecoder && doVppTranspose) ? "p010" : string.Empty) : "nv12";
 04365                var swapOutputWandH = isQsvDecoder && doVppTranspose && swapWAndH;
 04366                var hwScalePrefix = (isQsvDecoder && doVppTranspose) ? "vpp" : "scale";
 04367                var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, hwFilterSuffix, outFormat, swapOutputWandH, swpInW, 
 4368
 04369                if (!string.IsNullOrEmpty(hwScaleFilter) && isQsvDecoder && doVppTranspose)
 4370                {
 04371                    hwScaleFilter += $":transpose={tranposeDir}";
 4372                }
 4373
 4374                // allocate extra pool sizes for vaapi vpp scale
 04375                if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder)
 4376                {
 04377                    hwScaleFilter += ":extra_hw_frames=24";
 4378                }
 4379
 4380                // hw transpose(qsv vpp) & scale
 04381                mainFilters.Add(hwScaleFilter);
 4382            }
 4383
 4384            // vaapi vpp tonemap
 04385            if (doVaVppTonemap && isHwDecoder)
 4386            {
 04387                if (isQsvDecoder)
 4388                {
 4389                    // map from qsv to vaapi.
 04390                    mainFilters.Add("hwmap=derive_device=vaapi");
 04391                    mainFilters.Add("format=vaapi");
 4392                }
 4393
 04394                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12");
 04395                mainFilters.Add(tonemapFilter);
 4396
 04397                if (isQsvDecoder)
 4398                {
 4399                    // map from vaapi to qsv.
 04400                    mainFilters.Add("hwmap=derive_device=qsv");
 04401                    mainFilters.Add("format=qsv");
 4402                }
 4403            }
 4404
 04405            if (doOclTonemap && isHwDecoder)
 4406            {
 4407                // map from qsv to opencl via qsv(vaapi)-opencl interop.
 04408                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4409            }
 4410
 4411            // ocl tonemap
 04412            if (doOclTonemap)
 4413            {
 04414                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
 04415                mainFilters.Add(tonemapFilter);
 4416            }
 4417
 04418            var memoryOutput = false;
 04419            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04420            var isHwmapUsable = isSwEncoder && (doOclTonemap || isVaapiDecoder);
 04421            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4422            {
 04423                memoryOutput = true;
 4424
 4425                // OUTPUT nv12 surface(memory)
 4426                // prefer hwmap to hwdownload on opencl/vaapi.
 4427                // qsv hwmap is not fully implemented for the time being.
 04428                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04429                mainFilters.Add("format=nv12");
 4430            }
 4431
 4432            // OUTPUT nv12 surface(memory)
 04433            if (isSwDecoder && isQsvEncoder)
 4434            {
 04435                memoryOutput = true;
 4436            }
 4437
 04438            if (memoryOutput)
 4439            {
 4440                // text subtitles
 04441                if (hasTextSubs)
 4442                {
 04443                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04444                    mainFilters.Add(textSubtitlesFilter);
 4445                }
 4446            }
 4447
 04448            if (isQsvInQsvOut)
 4449            {
 04450                if (doOclTonemap)
 4451                {
 4452                    // OUTPUT qsv(nv12) surface(vram)
 4453                    // reverse-mapping via qsv(vaapi)-opencl interop.
 4454                    // add extra pool size to avoid the 'cannot allocate memory' error on hevc_qsv.
 04455                    mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1:extra_hw_frames=16");
 04456                    mainFilters.Add("format=qsv");
 4457                }
 04458                else if (isVaapiDecoder)
 4459                {
 04460                    mainFilters.Add("hwmap=derive_device=qsv");
 04461                    mainFilters.Add("format=qsv");
 4462                }
 4463            }
 4464
 4465            /* Make sub and overlay filters for subtitle stream */
 04466            var subFilters = new List<string>();
 04467            var overlayFilters = new List<string>();
 04468            if (isQsvInQsvOut)
 4469            {
 04470                if (hasSubs)
 4471                {
 04472                    if (hasGraphicalSubs)
 4473                    {
 4474                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04475                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04476                        subFilters.Add(subPreProcFilters);
 04477                        subFilters.Add("format=bgra");
 4478                    }
 04479                    else if (hasTextSubs)
 4480                    {
 04481                        var framerate = state.VideoStream?.RealFrameRate;
 04482                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4483
 04484                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04485                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04486                        subFilters.Add(alphaSrcFilter);
 04487                        subFilters.Add("format=bgra");
 04488                        subFilters.Add(subTextSubtitlesFilter);
 4489                    }
 4490
 4491                    // qsv requires a fixed pool size.
 4492                    // default to 64 otherwise it will fail on certain iGPU.
 04493                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4494
 04495                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04496                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04497                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04498                        : string.Empty;
 04499                    var overlayQsvFilter = string.Format(
 04500                        CultureInfo.InvariantCulture,
 04501                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04502                        overlaySize);
 04503                    overlayFilters.Add(overlayQsvFilter);
 4504                }
 4505            }
 04506            else if (memoryOutput)
 4507            {
 04508                if (hasGraphicalSubs)
 4509                {
 04510                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04511                    subFilters.Add(subPreProcFilters);
 04512                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4513                }
 4514            }
 4515
 04516            return (mainFilters, subFilters, overlayFilters);
 4517        }
 4518
 4519        /// <summary>
 4520        /// Gets the parameter of Intel/AMD VAAPI filter chain.
 4521        /// </summary>
 4522        /// <param name="state">Encoding state.</param>
 4523        /// <param name="options">Encoding options.</param>
 4524        /// <param name="vidEncoder">Video encoder to use.</param>
 4525        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4526        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiVidFilterChain(
 4527            EncodingJobInfo state,
 4528            EncodingOptions options,
 4529            string vidEncoder)
 4530        {
 04531            if (options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 4532            {
 04533                return (null, null, null);
 4534            }
 4535
 04536            var isLinux = OperatingSystem.IsLinux();
 04537            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04538            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04539            var isSwEncoder = !vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04540            var isVaapiFullSupported = isLinux && IsVaapiSupported(state) && IsVaapiFullSupported();
 04541            var isVaapiOclSupported = isVaapiFullSupported && IsOpenclFullSupported();
 04542            var isVaapiVkSupported = isVaapiFullSupported && IsVulkanFullSupported();
 4543
 4544            // legacy vaapi pipeline(copy-back)
 04545            if ((isSwDecoder && isSwEncoder)
 04546                || !isVaapiOclSupported
 04547                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4548            {
 04549                var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
 4550
 04551                if (!isSwEncoder)
 4552                {
 04553                    var newfilters = new List<string>();
 04554                    var noOverlay = swFilterChain.OverlayFilters.Count == 0;
 04555                    newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
 04556                    newfilters.Add("hwupload=derive_device=vaapi");
 4557
 04558                    var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
 04559                    var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
 04560                    return (mainFilters, swFilterChain.SubFilters, overlayFilters);
 4561                }
 4562
 04563                return swFilterChain;
 4564            }
 4565
 4566            // prefered vaapi + opencl filters pipeline
 04567            if (_mediaEncoder.IsVaapiDeviceInteliHD)
 4568            {
 4569                // Intel iHD path, with extra vpp tonemap and overlay support.
 04570                return GetIntelVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4571            }
 4572
 4573            // prefered vaapi + vulkan filters pipeline
 04574            if (_mediaEncoder.IsVaapiDeviceAmd
 04575                && isVaapiVkSupported
 04576                && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 04577                && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 4578            {
 4579                // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support.
 04580                return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4581            }
 4582
 4583            // Intel i965 and Amd legacy driver path, only featuring scale and deinterlace support.
 04584            return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4585        }
 4586
 4587        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVaapiFullVidFilt
 4588            EncodingJobInfo state,
 4589            EncodingOptions options,
 4590            string vidDecoder,
 4591            string vidEncoder)
 4592        {
 04593            var inW = state.VideoStream?.Width;
 04594            var inH = state.VideoStream?.Height;
 04595            var reqW = state.BaseRequest.Width;
 04596            var reqH = state.BaseRequest.Height;
 04597            var reqMaxW = state.BaseRequest.MaxWidth;
 04598            var reqMaxH = state.BaseRequest.MaxHeight;
 04599            var threeDFormat = state.MediaSource.Video3DFormat;
 4600
 04601            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04602            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04603            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04604            var isSwEncoder = !isVaapiEncoder;
 04605            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 4606
 04607            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04608            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04609            var doVaVppTonemap = isVaapiDecoder && IsIntelVppTonemapAvailable(state, options);
 04610            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04611            var doTonemap = doVaVppTonemap || doOclTonemap;
 04612            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4613
 04614            var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Enc
 04615            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04616            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04617            var hasAssSubs = hasSubs
 04618                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04619                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04620            var subW = state.SubtitleStream?.Width;
 04621            var subH = state.SubtitleStream?.Height;
 4622
 04623            var rotation = state.VideoStream?.Rotation ?? 0;
 04624            var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04625            var doVaVppTranspose = !string.IsNullOrEmpty(tranposeDir);
 04626            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVaVppTranspose));
 04627            var swpInW = swapWAndH ? inH : inW;
 04628            var swpInH = swapWAndH ? inW : inH;
 4629
 4630            /* Make main filters for video stream */
 04631            var mainFilters = new List<string>();
 4632
 04633            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4634
 04635            if (isSwDecoder)
 4636            {
 4637                // INPUT sw surface(memory)
 4638                // sw deint
 04639                if (doDeintH2645)
 4640                {
 04641                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04642                    mainFilters.Add(swDeintFilter);
 4643                }
 4644
 04645                var outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 04646                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 4647                // sw scale
 04648                mainFilters.Add(swScaleFilter);
 04649                mainFilters.Add($"format={outFormat}");
 4650
 4651                // keep video at memory except ocl tonemap,
 4652                // since the overhead caused by hwupload >>> using sw filter.
 4653                // sw => hw
 04654                if (doOclTonemap)
 4655                {
 04656                    mainFilters.Add("hwupload=derive_device=opencl");
 4657                }
 4658            }
 04659            else if (isVaapiDecoder)
 4660            {
 4661                // INPUT vaapi surface(vram)
 4662                // hw deint
 04663                if (doDeintH2645)
 4664                {
 04665                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 04666                    mainFilters.Add(deintFilter);
 4667                }
 4668
 4669                // hw transpose
 04670                if (doVaVppTranspose)
 4671                {
 04672                    mainFilters.Add($"transpose_vaapi=dir={tranposeDir}");
 4673                }
 4674
 04675                var outFormat = doTonemap ? string.Empty : "nv12";
 04676                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, swpInW, swpInH, reqW, reqH, req
 4677
 4678                // allocate extra pool sizes for vaapi vpp
 04679                if (!string.IsNullOrEmpty(hwScaleFilter))
 4680                {
 04681                    hwScaleFilter += ":extra_hw_frames=24";
 4682                }
 4683
 4684                // hw scale
 04685                mainFilters.Add(hwScaleFilter);
 4686            }
 4687
 4688            // vaapi vpp tonemap
 04689            if (doVaVppTonemap && isVaapiDecoder)
 4690            {
 04691                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12");
 04692                mainFilters.Add(tonemapFilter);
 4693            }
 4694
 04695            if (doOclTonemap && isVaapiDecoder)
 4696            {
 4697                // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 04698                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4699            }
 4700
 4701            // ocl tonemap
 04702            if (doOclTonemap)
 4703            {
 04704                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
 04705                mainFilters.Add(tonemapFilter);
 4706            }
 4707
 04708            if (doOclTonemap && isVaInVaOut)
 4709            {
 4710                // OUTPUT vaapi(nv12) surface(vram)
 4711                // reverse-mapping via vaapi-opencl interop.
 04712                mainFilters.Add("hwmap=derive_device=vaapi:mode=write:reverse=1");
 04713                mainFilters.Add("format=vaapi");
 4714            }
 4715
 04716            var memoryOutput = false;
 04717            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04718            var isHwmapNotUsable = isUploadForOclTonemap && isVaapiEncoder;
 04719            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap)
 4720            {
 04721                memoryOutput = true;
 4722
 4723                // OUTPUT nv12 surface(memory)
 4724                // prefer hwmap to hwdownload on opencl/vaapi.
 04725                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap=mode=read");
 04726                mainFilters.Add("format=nv12");
 4727            }
 4728
 4729            // OUTPUT nv12 surface(memory)
 04730            if (isSwDecoder && isVaapiEncoder)
 4731            {
 04732                memoryOutput = true;
 4733            }
 4734
 04735            if (memoryOutput)
 4736            {
 4737                // text subtitles
 04738                if (hasTextSubs)
 4739                {
 04740                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04741                    mainFilters.Add(textSubtitlesFilter);
 4742                }
 4743            }
 4744
 04745            if (memoryOutput && isVaapiEncoder)
 4746            {
 04747                if (!hasGraphicalSubs)
 4748                {
 04749                    mainFilters.Add("hwupload_vaapi");
 4750                }
 4751            }
 4752
 4753            /* Make sub and overlay filters for subtitle stream */
 04754            var subFilters = new List<string>();
 04755            var overlayFilters = new List<string>();
 04756            if (isVaInVaOut)
 4757            {
 04758                if (hasSubs)
 4759                {
 04760                    if (hasGraphicalSubs)
 4761                    {
 4762                        // overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04763                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04764                        subFilters.Add(subPreProcFilters);
 04765                        subFilters.Add("format=bgra");
 4766                    }
 04767                    else if (hasTextSubs)
 4768                    {
 04769                        var framerate = state.VideoStream?.RealFrameRate;
 04770                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4771
 04772                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04773                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04774                        subFilters.Add(alphaSrcFilter);
 04775                        subFilters.Add("format=bgra");
 04776                        subFilters.Add(subTextSubtitlesFilter);
 4777                    }
 4778
 04779                    subFilters.Add("hwupload=derive_device=vaapi");
 4780
 04781                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04782                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04783                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04784                        : string.Empty;
 04785                    var overlayVaapiFilter = string.Format(
 04786                        CultureInfo.InvariantCulture,
 04787                        "overlay_vaapi=eof_action=pass:repeatlast=0{0}",
 04788                        overlaySize);
 04789                    overlayFilters.Add(overlayVaapiFilter);
 4790                }
 4791            }
 04792            else if (memoryOutput)
 4793            {
 04794                if (hasGraphicalSubs)
 4795                {
 04796                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04797                    subFilters.Add(subPreProcFilters);
 04798                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4799
 04800                    if (isVaapiEncoder)
 4801                    {
 04802                        overlayFilters.Add("hwupload_vaapi");
 4803                    }
 4804                }
 4805            }
 4806
 04807            return (mainFilters, subFilters, overlayFilters);
 4808        }
 4809
 4810        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVaapiFullVidFilter
 4811            EncodingJobInfo state,
 4812            EncodingOptions options,
 4813            string vidDecoder,
 4814            string vidEncoder)
 4815        {
 04816            var inW = state.VideoStream?.Width;
 04817            var inH = state.VideoStream?.Height;
 04818            var reqW = state.BaseRequest.Width;
 04819            var reqH = state.BaseRequest.Height;
 04820            var reqMaxW = state.BaseRequest.MaxWidth;
 04821            var reqMaxH = state.BaseRequest.MaxHeight;
 04822            var threeDFormat = state.MediaSource.Video3DFormat;
 4823
 04824            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04825            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04826            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04827            var isSwEncoder = !isVaapiEncoder;
 4828
 04829            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04830            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04831            var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
 04832            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4833
 04834            var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Enc
 04835            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04836            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04837            var hasAssSubs = hasSubs
 04838                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04839                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 4840
 04841            var rotation = state.VideoStream?.Rotation ?? 0;
 04842            var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04843            var doVkTranspose = isVaapiDecoder && !string.IsNullOrEmpty(tranposeDir);
 04844            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVkTranspose));
 04845            var swpInW = swapWAndH ? inH : inW;
 04846            var swpInH = swapWAndH ? inW : inH;
 4847
 4848            /* Make main filters for video stream */
 04849            var mainFilters = new List<string>();
 4850
 04851            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doVkTonemap));
 4852
 04853            if (isSwDecoder)
 4854            {
 4855                // INPUT sw surface(memory)
 4856                // sw deint
 04857                if (doDeintH2645)
 4858                {
 04859                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04860                    mainFilters.Add(swDeintFilter);
 4861                }
 4862
 04863                if (doVkTonemap || hasSubs)
 4864                {
 4865                    // sw => hw
 04866                    mainFilters.Add("hwupload=derive_device=vulkan");
 04867                    mainFilters.Add("format=vulkan");
 4868                }
 4869                else
 4870                {
 4871                    // sw scale
 04872                    var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW,
 04873                    mainFilters.Add(swScaleFilter);
 04874                    mainFilters.Add("format=nv12");
 4875                }
 4876            }
 04877            else if (isVaapiDecoder)
 4878            {
 4879                // INPUT vaapi surface(vram)
 04880                if (doVkTranspose || doVkTonemap || hasSubs)
 4881                {
 4882                    // map from vaapi to vulkan/drm via interop (Polaris/gfx8+).
 04883                    if (_mediaEncoder.EncoderVersion >= _minFFmpegAlteredVaVkInterop)
 4884                    {
 04885                        if (doVkTranspose || !_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 4886                        {
 4887                            // disable the indirect va-drm-vk mapping since it's no longer reliable.
 04888                            mainFilters.Add("hwmap=derive_device=drm");
 04889                            mainFilters.Add("format=drm_prime");
 04890                            mainFilters.Add("hwmap=derive_device=vulkan");
 04891                            mainFilters.Add("format=vulkan");
 4892
 4893                            // workaround for libplacebo using the imported vulkan frame on gfx8.
 04894                            if (!_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 4895                            {
 04896                                mainFilters.Add("scale_vulkan");
 4897                            }
 4898                        }
 04899                        else if (doVkTonemap || hasSubs)
 4900                        {
 4901                            // non ad-hoc libplacebo also accepts drm_prime direct input.
 04902                            mainFilters.Add("hwmap=derive_device=drm");
 04903                            mainFilters.Add("format=drm_prime");
 4904                        }
 4905                    }
 4906                    else // legacy va-vk mapping that works only in jellyfin-ffmpeg6
 4907                    {
 04908                        mainFilters.Add("hwmap=derive_device=vulkan");
 04909                        mainFilters.Add("format=vulkan");
 4910                    }
 4911                }
 4912                else
 4913                {
 4914                    // hw deint
 04915                    if (doDeintH2645)
 4916                    {
 04917                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 04918                        mainFilters.Add(deintFilter);
 4919                    }
 4920
 4921                    // hw scale
 04922                    var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", "nv12", false, inW, inH, reqW, reqH, reqMaxW,
 04923                    mainFilters.Add(hwScaleFilter);
 4924                }
 4925            }
 4926
 4927            // vk transpose
 04928            if (doVkTranspose)
 4929            {
 04930                if (string.Equals(tranposeDir, "reversal", StringComparison.OrdinalIgnoreCase))
 4931                {
 04932                    mainFilters.Add("flip_vulkan");
 4933                }
 4934                else
 4935                {
 04936                    mainFilters.Add($"transpose_vulkan=dir={tranposeDir}");
 4937                }
 4938            }
 4939
 4940            // vk libplacebo
 04941            if (doVkTonemap || hasSubs)
 4942            {
 04943                var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, req
 04944                mainFilters.Add(libplaceboFilter);
 04945                mainFilters.Add("format=vulkan");
 4946            }
 4947
 04948            if (doVkTonemap && !hasSubs)
 4949            {
 4950                // OUTPUT vaapi(nv12) surface(vram)
 4951                // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 04952                mainFilters.Add("hwmap=derive_device=vaapi");
 04953                mainFilters.Add("format=vaapi");
 4954
 4955                // clear the surf->meta_offset and output nv12
 04956                mainFilters.Add("scale_vaapi=format=nv12");
 4957
 4958                // hw deint
 04959                if (doDeintH2645)
 4960                {
 04961                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 04962                    mainFilters.Add(deintFilter);
 4963                }
 4964            }
 4965
 04966            if (!hasSubs)
 4967            {
 4968                // OUTPUT nv12 surface(memory)
 04969                if (isSwEncoder && (doVkTonemap || isVaapiDecoder))
 4970                {
 04971                    mainFilters.Add("hwdownload");
 04972                    mainFilters.Add("format=nv12");
 4973                }
 4974
 04975                if (isSwDecoder && isVaapiEncoder && !doVkTonemap)
 4976                {
 04977                    mainFilters.Add("hwupload_vaapi");
 4978                }
 4979            }
 4980
 4981            /* Make sub and overlay filters for subtitle stream */
 04982            var subFilters = new List<string>();
 04983            var overlayFilters = new List<string>();
 04984            if (hasSubs)
 4985            {
 04986                if (hasGraphicalSubs)
 4987                {
 04988                    var subW = state.SubtitleStream?.Width;
 04989                    var subH = state.SubtitleStream?.Height;
 04990                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04991                    subFilters.Add(subPreProcFilters);
 04992                    subFilters.Add("format=bgra");
 4993                }
 04994                else if (hasTextSubs)
 4995                {
 04996                    var framerate = state.VideoStream?.RealFrameRate;
 04997                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4998
 04999                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05000                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05001                    subFilters.Add(alphaSrcFilter);
 05002                    subFilters.Add("format=bgra");
 05003                    subFilters.Add(subTextSubtitlesFilter);
 5004                }
 5005
 05006                subFilters.Add("hwupload=derive_device=vulkan");
 05007                subFilters.Add("format=vulkan");
 5008
 05009                overlayFilters.Add("overlay_vulkan=eof_action=pass:repeatlast=0");
 5010
 05011                if (isSwEncoder)
 5012                {
 5013                    // OUTPUT nv12 surface(memory)
 05014                    overlayFilters.Add("scale_vulkan=format=nv12");
 05015                    overlayFilters.Add("hwdownload");
 05016                    overlayFilters.Add("format=nv12");
 5017                }
 05018                else if (isVaapiEncoder)
 5019                {
 5020                    // OUTPUT vaapi(nv12) surface(vram)
 5021                    // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05022                    overlayFilters.Add("hwmap=derive_device=vaapi");
 05023                    overlayFilters.Add("format=vaapi");
 5024
 5025                    // clear the surf->meta_offset and output nv12
 05026                    overlayFilters.Add("scale_vaapi=format=nv12");
 5027
 5028                    // hw deint
 05029                    if (doDeintH2645)
 5030                    {
 05031                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05032                        overlayFilters.Add(deintFilter);
 5033                    }
 5034                }
 5035            }
 5036
 05037            return (mainFilters, subFilters, overlayFilters);
 5038        }
 5039
 5040        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiLimitedVidFilter
 5041            EncodingJobInfo state,
 5042            EncodingOptions options,
 5043            string vidDecoder,
 5044            string vidEncoder)
 5045        {
 05046            var inW = state.VideoStream?.Width;
 05047            var inH = state.VideoStream?.Height;
 05048            var reqW = state.BaseRequest.Width;
 05049            var reqH = state.BaseRequest.Height;
 05050            var reqMaxW = state.BaseRequest.MaxWidth;
 05051            var reqMaxH = state.BaseRequest.MaxHeight;
 05052            var threeDFormat = state.MediaSource.Video3DFormat;
 5053
 05054            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05055            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05056            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05057            var isSwEncoder = !isVaapiEncoder;
 05058            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 05059            var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965;
 05060            var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd;
 5061
 05062            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05063            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05064            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05065            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5066
 05067            var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Enc
 05068            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05069            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 5070
 05071            var rotation = state.VideoStream?.Rotation ?? 0;
 05072            var swapWAndH = Math.Abs(rotation) == 90 && isSwDecoder;
 05073            var swpInW = swapWAndH ? inH : inW;
 05074            var swpInH = swapWAndH ? inW : inH;
 5075
 5076            /* Make main filters for video stream */
 05077            var mainFilters = new List<string>();
 5078
 05079            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5080
 05081            var outFormat = string.Empty;
 05082            if (isSwDecoder)
 5083            {
 5084                // INPUT sw surface(memory)
 5085                // sw deint
 05086                if (doDeintH2645)
 5087                {
 05088                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05089                    mainFilters.Add(swDeintFilter);
 5090                }
 5091
 05092                outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05093                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 5094                // sw scale
 05095                mainFilters.Add(swScaleFilter);
 05096                mainFilters.Add("format=" + outFormat);
 5097
 5098                // keep video at memory except ocl tonemap,
 5099                // since the overhead caused by hwupload >>> using sw filter.
 5100                // sw => hw
 05101                if (doOclTonemap)
 5102                {
 05103                    mainFilters.Add("hwupload=derive_device=opencl");
 5104                }
 5105            }
 05106            else if (isVaapiDecoder)
 5107            {
 5108                // INPUT vaapi surface(vram)
 5109                // hw deint
 05110                if (doDeintH2645)
 5111                {
 05112                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05113                    mainFilters.Add(deintFilter);
 5114                }
 5115
 05116                outFormat = doOclTonemap ? string.Empty : "nv12";
 05117                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, inW, inH, reqW, reqH, reqMaxW, 
 5118
 5119                // allocate extra pool sizes for vaapi vpp
 05120                if (!string.IsNullOrEmpty(hwScaleFilter))
 5121                {
 05122                    hwScaleFilter += ":extra_hw_frames=24";
 5123                }
 5124
 5125                // hw scale
 05126                mainFilters.Add(hwScaleFilter);
 5127            }
 5128
 05129            if (doOclTonemap && isVaapiDecoder)
 5130            {
 05131                if (isi965Driver)
 5132                {
 5133                    // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05134                    mainFilters.Add("hwmap=derive_device=opencl");
 5135                }
 5136                else
 5137                {
 05138                    mainFilters.Add("hwdownload");
 05139                    mainFilters.Add("format=p010le");
 05140                    mainFilters.Add("hwupload=derive_device=opencl");
 5141                }
 5142            }
 5143
 5144            // ocl tonemap
 05145            if (doOclTonemap)
 5146            {
 05147                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
 05148                mainFilters.Add(tonemapFilter);
 5149            }
 5150
 05151            if (doOclTonemap && isVaInVaOut)
 5152            {
 05153                if (isi965Driver)
 5154                {
 5155                    // OUTPUT vaapi(nv12) surface(vram)
 5156                    // reverse-mapping via vaapi-opencl interop.
 05157                    mainFilters.Add("hwmap=derive_device=vaapi:reverse=1");
 05158                    mainFilters.Add("format=vaapi");
 5159                }
 5160            }
 5161
 05162            var memoryOutput = false;
 05163            var isUploadForOclTonemap = doOclTonemap && (isSwDecoder || (isVaapiDecoder && !isi965Driver));
 05164            var isHwmapNotUsable = hasGraphicalSubs || isUploadForOclTonemap;
 05165            var isHwmapForSubs = hasSubs && isVaapiDecoder;
 05166            var isHwUnmapForTextSubs = hasTextSubs && isVaInVaOut && !isUploadForOclTonemap;
 05167            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap || isHwmapForSubs)
 5168            {
 05169                memoryOutput = true;
 5170
 5171                // OUTPUT nv12 surface(memory)
 5172                // prefer hwmap to hwdownload on opencl/vaapi.
 05173                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap");
 05174                mainFilters.Add("format=nv12");
 5175            }
 5176
 5177            // OUTPUT nv12 surface(memory)
 05178            if (isSwDecoder && isVaapiEncoder)
 5179            {
 05180                memoryOutput = true;
 5181            }
 5182
 05183            if (memoryOutput)
 5184            {
 5185                // text subtitles
 05186                if (hasTextSubs)
 5187                {
 05188                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05189                    mainFilters.Add(textSubtitlesFilter);
 5190                }
 5191            }
 5192
 05193            if (isHwUnmapForTextSubs)
 5194            {
 05195                mainFilters.Add("hwmap");
 05196                mainFilters.Add("format=vaapi");
 5197            }
 05198            else if (memoryOutput && isVaapiEncoder)
 5199            {
 05200                if (!hasGraphicalSubs)
 5201                {
 05202                    mainFilters.Add("hwupload_vaapi");
 5203                }
 5204            }
 5205
 5206            /* Make sub and overlay filters for subtitle stream */
 05207            var subFilters = new List<string>();
 05208            var overlayFilters = new List<string>();
 05209            if (memoryOutput)
 5210            {
 05211                if (hasGraphicalSubs)
 5212                {
 05213                    var subW = state.SubtitleStream?.Width;
 05214                    var subH = state.SubtitleStream?.Height;
 05215                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05216                    subFilters.Add(subPreProcFilters);
 05217                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5218
 05219                    if (isVaapiEncoder)
 5220                    {
 05221                        overlayFilters.Add("hwupload_vaapi");
 5222                    }
 5223                }
 5224            }
 5225
 05226            return (mainFilters, subFilters, overlayFilters);
 5227        }
 5228
 5229        /// <summary>
 5230        /// Gets the parameter of Apple VideoToolBox filter chain.
 5231        /// </summary>
 5232        /// <param name="state">Encoding state.</param>
 5233        /// <param name="options">Encoding options.</param>
 5234        /// <param name="vidEncoder">Video encoder to use.</param>
 5235        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5236        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFilterChain(
 5237            EncodingJobInfo state,
 5238            EncodingOptions options,
 5239            string vidEncoder)
 5240        {
 05241            if (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 5242            {
 05243                return (null, null, null);
 5244            }
 5245
 5246            // ReSharper disable once InconsistentNaming
 05247            var isMacOS = OperatingSystem.IsMacOS();
 05248            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05249            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05250            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05251            var isVtFullSupported = isMacOS && IsVideoToolboxFullSupported();
 5252
 5253            // legacy videotoolbox pipeline (disable hw filters)
 05254            if (!(isVtEncoder || isVtDecoder)
 05255                || !isVtFullSupported
 05256                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5257            {
 05258                return GetSwVidFilterChain(state, options, vidEncoder);
 5259            }
 5260
 5261            // preferred videotoolbox + metal filters pipeline
 05262            return GetAppleVidFiltersPreferred(state, options, vidDecoder, vidEncoder);
 5263        }
 5264
 5265        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFiltersPrefer
 5266            EncodingJobInfo state,
 5267            EncodingOptions options,
 5268            string vidDecoder,
 5269            string vidEncoder)
 5270        {
 05271            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05272            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 5273
 05274            var inW = state.VideoStream?.Width;
 05275            var inH = state.VideoStream?.Height;
 05276            var reqW = state.BaseRequest.Width;
 05277            var reqH = state.BaseRequest.Height;
 05278            var reqMaxW = state.BaseRequest.MaxWidth;
 05279            var reqMaxH = state.BaseRequest.MaxHeight;
 05280            var threeDFormat = state.MediaSource.Video3DFormat;
 5281
 05282            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05283            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05284            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05285            var doVtTonemap = IsVideoToolboxTonemapAvailable(state, options);
 05286            var doMetalTonemap = !doVtTonemap && IsHwTonemapAvailable(state, options);
 05287            var usingHwSurface = isVtDecoder && (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface);
 5288
 05289            var rotation = state.VideoStream?.Rotation ?? 0;
 05290            var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05291            var doVtTranspose = !string.IsNullOrEmpty(tranposeDir) && _mediaEncoder.SupportsFilter("transpose_vt");
 05292            var swapWAndH = Math.Abs(rotation) == 90 && doVtTranspose;
 05293            var swpInW = swapWAndH ? inH : inW;
 05294            var swpInH = swapWAndH ? inW : inH;
 5295
 05296            var scaleFormat = string.Empty;
 5297            // Use P010 for Metal tone mapping, otherwise force an 8bit output.
 05298            if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase))
 5299            {
 05300                if (doMetalTonemap)
 5301                {
 05302                    if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 5303                    {
 05304                        scaleFormat = "p010le";
 5305                    }
 5306                }
 5307                else
 5308                {
 05309                    scaleFormat = "nv12";
 5310                }
 5311            }
 5312
 05313            var hwScaleFilter = GetHwScaleFilter("scale", "vt", scaleFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW,
 5314
 05315            var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Enc
 05316            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05317            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05318            var hasAssSubs = hasSubs
 05319                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05320                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5321
 5322            /* Make main filters for video stream */
 05323            var mainFilters = new List<string>();
 5324
 5325            // hw deint
 05326            if (doDeintH2645)
 5327            {
 05328                var deintFilter = GetHwDeinterlaceFilter(state, options, "videotoolbox");
 05329                mainFilters.Add(deintFilter);
 5330            }
 5331
 5332            // hw transpose
 05333            if (doVtTranspose)
 5334            {
 05335                mainFilters.Add($"transpose_vt=dir={tranposeDir}");
 5336            }
 5337
 05338            if (doVtTonemap)
 5339            {
 5340                const string VtTonemapArgs = "color_matrix=bt709:color_primaries=bt709:color_transfer=bt709";
 5341
 5342                // scale_vt can handle scaling & tonemapping in one shot, just like vpp_qsv.
 05343                hwScaleFilter = string.IsNullOrEmpty(hwScaleFilter)
 05344                    ? "scale_vt=" + VtTonemapArgs
 05345                    : hwScaleFilter + ":" + VtTonemapArgs;
 5346            }
 5347
 5348            // hw scale & vt tonemap
 05349            mainFilters.Add(hwScaleFilter);
 5350
 5351            // Metal tonemap
 05352            if (doMetalTonemap)
 5353            {
 05354                var tonemapFilter = GetHwTonemapFilter(options, "videotoolbox", "nv12");
 05355                mainFilters.Add(tonemapFilter);
 5356            }
 5357
 5358            /* Make sub and overlay filters for subtitle stream */
 05359            var subFilters = new List<string>();
 05360            var overlayFilters = new List<string>();
 5361
 05362            if (hasSubs)
 5363            {
 05364                if (hasGraphicalSubs)
 5365                {
 05366                    var subW = state.SubtitleStream?.Width;
 05367                    var subH = state.SubtitleStream?.Height;
 05368                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05369                    subFilters.Add(subPreProcFilters);
 05370                    subFilters.Add("format=bgra");
 5371                }
 05372                else if (hasTextSubs)
 5373                {
 05374                    var framerate = state.VideoStream?.RealFrameRate;
 05375                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5376
 05377                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05378                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05379                    subFilters.Add(alphaSrcFilter);
 05380                    subFilters.Add("format=bgra");
 05381                    subFilters.Add(subTextSubtitlesFilter);
 5382                }
 5383
 05384                subFilters.Add("hwupload");
 05385                overlayFilters.Add("overlay_videotoolbox=eof_action=pass:repeatlast=0");
 5386            }
 5387
 05388            if (usingHwSurface)
 5389            {
 05390                if (!isVtEncoder)
 5391                {
 05392                    mainFilters.Add("hwdownload");
 05393                    mainFilters.Add("format=nv12");
 5394                }
 5395
 05396                return (mainFilters, subFilters, overlayFilters);
 5397            }
 5398
 5399            // For old jellyfin-ffmpeg that has broken hwsurface, add a hwupload
 05400            var needFiltering = mainFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05401                                subFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05402                                overlayFilters.Any(f => !string.IsNullOrEmpty(f));
 05403            if (needFiltering)
 5404            {
 5405                // INPUT videotoolbox/memory surface(vram/uma)
 5406                // this will pass-through automatically if in/out format matches.
 05407                mainFilters.Insert(0, "hwupload");
 05408                mainFilters.Insert(0, "format=nv12|p010le|videotoolbox_vld");
 5409
 05410                if (!isVtEncoder)
 5411                {
 05412                    mainFilters.Add("hwdownload");
 05413                    mainFilters.Add("format=nv12");
 5414                }
 5415            }
 5416
 05417            return (mainFilters, subFilters, overlayFilters);
 5418        }
 5419
 5420        /// <summary>
 5421        /// Gets the parameter of Rockchip RKMPP/RKRGA filter chain.
 5422        /// </summary>
 5423        /// <param name="state">Encoding state.</param>
 5424        /// <param name="options">Encoding options.</param>
 5425        /// <param name="vidEncoder">Video encoder to use.</param>
 5426        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5427        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFilterChain(
 5428            EncodingJobInfo state,
 5429            EncodingOptions options,
 5430            string vidEncoder)
 5431        {
 05432            if (options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 5433            {
 05434                return (null, null, null);
 5435            }
 5436
 05437            var isLinux = OperatingSystem.IsLinux();
 05438            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05439            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05440            var isSwEncoder = !vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05441            var isRkmppOclSupported = isLinux && IsRkmppFullSupported() && IsOpenclFullSupported();
 5442
 05443            if ((isSwDecoder && isSwEncoder)
 05444                || !isRkmppOclSupported
 05445                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5446            {
 05447                return GetSwVidFilterChain(state, options, vidEncoder);
 5448            }
 5449
 5450            // prefered rkmpp + rkrga + opencl filters pipeline
 05451            if (isRkmppOclSupported)
 5452            {
 05453                return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5454            }
 5455
 05456            return (null, null, null);
 5457        }
 5458
 5459        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFiltersPrefer
 5460            EncodingJobInfo state,
 5461            EncodingOptions options,
 5462            string vidDecoder,
 5463            string vidEncoder)
 5464        {
 05465            var inW = state.VideoStream?.Width;
 05466            var inH = state.VideoStream?.Height;
 05467            var reqW = state.BaseRequest.Width;
 05468            var reqH = state.BaseRequest.Height;
 05469            var reqMaxW = state.BaseRequest.MaxWidth;
 05470            var reqMaxH = state.BaseRequest.MaxHeight;
 05471            var threeDFormat = state.MediaSource.Video3DFormat;
 5472
 05473            var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05474            var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05475            var isSwDecoder = !isRkmppDecoder;
 05476            var isSwEncoder = !isRkmppEncoder;
 05477            var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder;
 05478            var isEncoderSupportAfbc = isRkmppEncoder
 05479                && (vidEncoder.Contains("h264", StringComparison.OrdinalIgnoreCase)
 05480                    || vidEncoder.Contains("hevc", StringComparison.OrdinalIgnoreCase));
 5481
 05482            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05483            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05484            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05485            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5486
 05487            var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
 05488            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05489            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05490            var hasAssSubs = hasSubs
 05491                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05492                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 05493            var subW = state.SubtitleStream?.Width;
 05494            var subH = state.SubtitleStream?.Height;
 5495
 05496            var rotation = state.VideoStream?.Rotation ?? 0;
 05497            var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05498            var doRkVppTranspose = !string.IsNullOrEmpty(tranposeDir);
 05499            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isRkmppDecoder && doRkVppTranspose));
 05500            var swpInW = swapWAndH ? inH : inW;
 05501            var swpInH = swapWAndH ? inW : inH;
 5502
 5503            /* Make main filters for video stream */
 05504            var mainFilters = new List<string>();
 5505
 05506            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5507
 05508            if (isSwDecoder)
 5509            {
 5510                // INPUT sw surface(memory)
 5511                // sw deint
 05512                if (doDeintH2645)
 5513                {
 05514                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05515                    mainFilters.Add(swDeintFilter);
 5516                }
 5517
 05518                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 05519                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05520                if (!string.IsNullOrEmpty(swScaleFilter))
 5521                {
 05522                    swScaleFilter += ":flags=fast_bilinear";
 5523                }
 5524
 5525                // sw scale
 05526                mainFilters.Add(swScaleFilter);
 05527                mainFilters.Add($"format={outFormat}");
 5528
 5529                // keep video at memory except ocl tonemap,
 5530                // since the overhead caused by hwupload >>> using sw filter.
 5531                // sw => hw
 05532                if (doOclTonemap)
 5533                {
 05534                    mainFilters.Add("hwupload=derive_device=opencl");
 5535                }
 5536            }
 05537            else if (isRkmppDecoder)
 5538            {
 5539                // INPUT rkmpp/drm surface(gem/dma-heap)
 5540
 05541                var isFullAfbcPipeline = isEncoderSupportAfbc && isDrmInDrmOut && !doOclTonemap;
 05542                var swapOutputWandH = doRkVppTranspose && swapWAndH;
 05543                var outFormat = doOclTonemap ? "p010" : "nv12";
 05544                var hwScalePrefix = doRkVppTranspose ? "vpp" : "scale";
 05545                var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, "rkrga", outFormat, swapOutputWandH, swpInW, swpInH,
 05546                var hwScaleFilter2 = GetHwScaleFilter(hwScalePrefix, "rkrga", string.Empty, swapOutputWandH, swpInW, swp
 5547
 05548                if (!hasSubs
 05549                     || doRkVppTranspose
 05550                     || !isFullAfbcPipeline
 05551                     || !string.IsNullOrEmpty(hwScaleFilter2))
 5552                {
 05553                    if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose)
 5554                    {
 05555                        hwScaleFilter += $":transpose={tranposeDir}";
 5556                    }
 5557
 5558                    // try enabling AFBC to save DDR bandwidth
 05559                    if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline)
 5560                    {
 05561                        hwScaleFilter += ":afbc=1";
 5562                    }
 5563
 5564                    // hw transpose & scale
 05565                    mainFilters.Add(hwScaleFilter);
 5566                }
 5567            }
 5568
 05569            if (doOclTonemap && isRkmppDecoder)
 5570            {
 5571                // map from rkmpp/drm to opencl via drm-opencl interop.
 05572                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 5573            }
 5574
 5575            // ocl tonemap
 05576            if (doOclTonemap)
 5577            {
 05578                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
 05579                mainFilters.Add(tonemapFilter);
 5580            }
 5581
 05582            var memoryOutput = false;
 05583            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 05584            if ((isRkmppDecoder && isSwEncoder) || isUploadForOclTonemap)
 5585            {
 05586                memoryOutput = true;
 5587
 5588                // OUTPUT nv12 surface(memory)
 05589                mainFilters.Add("hwdownload");
 05590                mainFilters.Add("format=nv12");
 5591            }
 5592
 5593            // OUTPUT nv12 surface(memory)
 05594            if (isSwDecoder && isRkmppEncoder)
 5595            {
 05596                memoryOutput = true;
 5597            }
 5598
 05599            if (memoryOutput)
 5600            {
 5601                // text subtitles
 05602                if (hasTextSubs)
 5603                {
 05604                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05605                    mainFilters.Add(textSubtitlesFilter);
 5606                }
 5607            }
 5608
 05609            if (isDrmInDrmOut)
 5610            {
 05611                if (doOclTonemap)
 5612                {
 5613                    // OUTPUT drm(nv12) surface(gem/dma-heap)
 5614                    // reverse-mapping via drm-opencl interop.
 05615                    mainFilters.Add("hwmap=derive_device=rkmpp:mode=write:reverse=1");
 05616                    mainFilters.Add("format=drm_prime");
 5617                }
 5618            }
 5619
 5620            /* Make sub and overlay filters for subtitle stream */
 05621            var subFilters = new List<string>();
 05622            var overlayFilters = new List<string>();
 05623            if (isDrmInDrmOut)
 5624            {
 05625                if (hasSubs)
 5626                {
 05627                    if (hasGraphicalSubs)
 5628                    {
 05629                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 05630                        subFilters.Add(subPreProcFilters);
 05631                        subFilters.Add("format=bgra");
 5632                    }
 05633                    else if (hasTextSubs)
 5634                    {
 05635                        var framerate = state.VideoStream?.RealFrameRate;
 05636                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5637
 5638                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 05639                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 05640                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05641                        subFilters.Add(alphaSrcFilter);
 05642                        subFilters.Add("format=bgra");
 05643                        subFilters.Add(subTextSubtitlesFilter);
 5644                    }
 5645
 05646                    subFilters.Add("hwupload=derive_device=rkmpp");
 5647
 5648                    // try enabling AFBC to save DDR bandwidth
 05649                    var hwOverlayFilter = "overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12";
 05650                    if (isEncoderSupportAfbc)
 5651                    {
 05652                        hwOverlayFilter += ":afbc=1";
 5653                    }
 5654
 05655                    overlayFilters.Add(hwOverlayFilter);
 5656                }
 5657            }
 05658            else if (memoryOutput)
 5659            {
 05660                if (hasGraphicalSubs)
 5661                {
 05662                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05663                    subFilters.Add(subPreProcFilters);
 05664                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5665                }
 5666            }
 5667
 05668            return (mainFilters, subFilters, overlayFilters);
 5669        }
 5670
 5671        /// <summary>
 5672        /// Gets the parameter of video processing filters.
 5673        /// </summary>
 5674        /// <param name="state">Encoding state.</param>
 5675        /// <param name="options">Encoding options.</param>
 5676        /// <param name="outputVideoCodec">Video codec to use.</param>
 5677        /// <returns>The video processing filters parameter.</returns>
 5678        public string GetVideoProcessingFilterParam(
 5679            EncodingJobInfo state,
 5680            EncodingOptions options,
 5681            string outputVideoCodec)
 5682        {
 05683            var videoStream = state.VideoStream;
 05684            if (videoStream is null)
 5685            {
 05686                return string.Empty;
 5687            }
 5688
 05689            var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Enc
 05690            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05691            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 5692
 5693            List<string> mainFilters;
 5694            List<string> subFilters;
 5695            List<string> overlayFilters;
 5696
 05697            (mainFilters, subFilters, overlayFilters) = options.HardwareAccelerationType switch
 05698            {
 05699                HardwareAccelerationType.vaapi => GetVaapiVidFilterChain(state, options, outputVideoCodec),
 05700                HardwareAccelerationType.amf => GetAmdVidFilterChain(state, options, outputVideoCodec),
 05701                HardwareAccelerationType.qsv => GetIntelVidFilterChain(state, options, outputVideoCodec),
 05702                HardwareAccelerationType.nvenc => GetNvidiaVidFilterChain(state, options, outputVideoCodec),
 05703                HardwareAccelerationType.videotoolbox => GetAppleVidFilterChain(state, options, outputVideoCodec),
 05704                HardwareAccelerationType.rkmpp => GetRkmppVidFilterChain(state, options, outputVideoCodec),
 05705                _ => GetSwVidFilterChain(state, options, outputVideoCodec),
 05706            };
 5707
 05708            mainFilters?.RemoveAll(string.IsNullOrEmpty);
 05709            subFilters?.RemoveAll(string.IsNullOrEmpty);
 05710            overlayFilters?.RemoveAll(string.IsNullOrEmpty);
 5711
 05712            var framerate = GetFramerateParam(state);
 05713            if (framerate.HasValue)
 5714            {
 05715                mainFilters.Insert(0, string.Format(
 05716                    CultureInfo.InvariantCulture,
 05717                    "fps={0}",
 05718                    framerate.Value));
 5719            }
 5720
 05721            var mainStr = string.Empty;
 05722            if (mainFilters?.Count > 0)
 5723            {
 05724                mainStr = string.Format(
 05725                    CultureInfo.InvariantCulture,
 05726                    "{0}",
 05727                    string.Join(',', mainFilters));
 5728            }
 5729
 05730            if (overlayFilters?.Count == 0)
 5731            {
 5732                // -vf "scale..."
 05733                return string.IsNullOrEmpty(mainStr) ? string.Empty : " -vf \"" + mainStr + "\"";
 5734            }
 5735
 05736            if (overlayFilters?.Count > 0
 05737                && subFilters?.Count > 0
 05738                && state.SubtitleStream is not null)
 5739            {
 5740                // overlay graphical/text subtitles
 05741                var subStr = string.Format(
 05742                        CultureInfo.InvariantCulture,
 05743                        "{0}",
 05744                        string.Join(',', subFilters));
 5745
 05746                var overlayStr = string.Format(
 05747                        CultureInfo.InvariantCulture,
 05748                        "{0}",
 05749                        string.Join(',', overlayFilters));
 5750
 05751                var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
 05752                var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 05753                var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 5754
 05755                if (hasSubs)
 5756                {
 5757                    // -filter_complex "[0:s]scale=s[sub]..."
 05758                    var filterStr = string.IsNullOrEmpty(mainStr)
 05759                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]{5}\""
 05760                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 5761
 05762                    if (hasTextSubs)
 5763                    {
 05764                        filterStr = string.IsNullOrEmpty(mainStr)
 05765                            ? " -filter_complex \"{4}[sub];[0:{2}][sub]{5}\""
 05766                            : " -filter_complex \"{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 5767                    }
 5768
 05769                    return string.Format(
 05770                        CultureInfo.InvariantCulture,
 05771                        filterStr,
 05772                        mapPrefix,
 05773                        subtitleStreamIndex,
 05774                        videoStreamIndex,
 05775                        mainStr,
 05776                        subStr,
 05777                        overlayStr);
 5778                }
 5779            }
 5780
 05781            return string.Empty;
 5782        }
 5783
 5784        public string GetOverwriteColorPropertiesParam(EncodingJobInfo state, bool isTonemapAvailable)
 5785        {
 05786            if (isTonemapAvailable)
 5787            {
 05788                return GetInputHdrParam(state.VideoStream?.ColorTransfer);
 5789            }
 5790
 05791            return GetOutputSdrParam(null);
 5792        }
 5793
 5794        public string GetInputHdrParam(string colorTransfer)
 5795        {
 05796            if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
 5797            {
 5798                // HLG
 05799                return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
 5800            }
 5801
 5802            // HDR10
 05803            return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc";
 5804        }
 5805
 5806        public string GetOutputSdrParam(string tonemappingRange)
 5807        {
 5808            // SDR
 05809            if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
 5810            {
 05811                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
 5812            }
 5813
 05814            if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
 5815            {
 05816                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc";
 5817            }
 5818
 05819            return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
 5820        }
 5821
 5822        public static int GetVideoColorBitDepth(EncodingJobInfo state)
 5823        {
 05824            var videoStream = state.VideoStream;
 05825            if (videoStream is not null)
 5826            {
 05827                if (videoStream.BitDepth.HasValue)
 5828                {
 05829                    return videoStream.BitDepth.Value;
 5830                }
 5831
 05832                if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
 05833                    || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
 05834                    || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 05835                    || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
 5836                {
 05837                    return 8;
 5838                }
 5839
 05840                if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 05841                    || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 05842                    || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
 5843                {
 05844                    return 10;
 5845                }
 5846
 05847                if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 05848                    || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 05849                    || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
 5850                {
 05851                    return 12;
 5852                }
 5853
 05854                return 8;
 5855            }
 5856
 05857            return 0;
 5858        }
 5859
 5860        /// <summary>
 5861        /// Gets the ffmpeg option string for the hardware accelerated video decoder.
 5862        /// </summary>
 5863        /// <param name="state">The encoding job info.</param>
 5864        /// <param name="options">The encoding options.</param>
 5865        /// <returns>The option string or null if none available.</returns>
 5866        protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
 5867        {
 05868            var videoStream = state.VideoStream;
 05869            var mediaSource = state.MediaSource;
 05870            if (videoStream is null || mediaSource is null)
 5871            {
 05872                return null;
 5873            }
 5874
 5875            // HWA decoders can handle both video files and video folders.
 05876            var videoType = state.VideoType;
 05877            if (videoType != VideoType.VideoFile
 05878                && videoType != VideoType.Iso
 05879                && videoType != VideoType.Dvd
 05880                && videoType != VideoType.BluRay)
 5881            {
 05882                return null;
 5883            }
 5884
 05885            if (IsCopyCodec(state.OutputVideoCodec))
 5886            {
 05887                return null;
 5888            }
 5889
 05890            var hardwareAccelerationType = options.HardwareAccelerationType;
 5891
 05892            if (!string.IsNullOrEmpty(videoStream.Codec) && hardwareAccelerationType != HardwareAccelerationType.none)
 5893            {
 05894                var bitDepth = GetVideoColorBitDepth(state);
 5895
 5896                // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support for most platforms
 05897                if (bitDepth == 10
 05898                    && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 05899                         || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
 05900                         || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)
 05901                         || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)))
 5902                {
 5903                    // RKMPP has H.264 Hi10P decoder
 05904                    bool hasHardwareHi10P = hardwareAccelerationType == HardwareAccelerationType.rkmpp;
 5905
 5906                    // VideoToolbox on Apple Silicon has H.264 Hi10P mode enabled after macOS 14.6
 05907                    if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox)
 5908                    {
 05909                        var ver = Environment.OSVersion.Version;
 05910                        var arch = RuntimeInformation.OSArchitecture;
 05911                        if (arch.Equals(Architecture.Arm64) && ver >= new Version(14, 6))
 5912                        {
 05913                            hasHardwareHi10P = true;
 5914                        }
 5915                    }
 5916
 05917                    if (!hasHardwareHi10P
 05918                        && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 5919                    {
 05920                        return null;
 5921                    }
 5922                }
 5923
 05924                var decoder = hardwareAccelerationType switch
 05925                {
 05926                    HardwareAccelerationType.vaapi => GetVaapiVidDecoder(state, options, videoStream, bitDepth),
 05927                    HardwareAccelerationType.amf => GetAmfVidDecoder(state, options, videoStream, bitDepth),
 05928                    HardwareAccelerationType.qsv => GetQsvHwVidDecoder(state, options, videoStream, bitDepth),
 05929                    HardwareAccelerationType.nvenc => GetNvdecVidDecoder(state, options, videoStream, bitDepth),
 05930                    HardwareAccelerationType.videotoolbox => GetVideotoolboxVidDecoder(state, options, videoStream, bitD
 05931                    HardwareAccelerationType.rkmpp => GetRkmppVidDecoder(state, options, videoStream, bitDepth),
 05932                    _ => string.Empty
 05933                };
 5934
 05935                if (!string.IsNullOrEmpty(decoder))
 5936                {
 05937                    return decoder;
 5938                }
 5939            }
 5940
 05941            var whichCodec = videoStream.Codec;
 05942            if (string.Equals(whichCodec, "avc", StringComparison.OrdinalIgnoreCase))
 5943            {
 05944                whichCodec = "h264";
 5945            }
 05946            else if (string.Equals(whichCodec, "h265", StringComparison.OrdinalIgnoreCase))
 5947            {
 05948                whichCodec = "hevc";
 5949            }
 5950
 5951            // Avoid a second attempt if no hardware acceleration is being used
 05952            options.HardwareDecodingCodecs = options.HardwareDecodingCodecs.Where(c => !string.Equals(c, whichCodec, Str
 5953
 5954            // leave blank so ffmpeg will decide
 05955            return null;
 5956        }
 5957
 5958        /// <summary>
 5959        /// Gets a hw decoder name.
 5960        /// </summary>
 5961        /// <param name="options">Encoding options.</param>
 5962        /// <param name="decoderPrefix">Decoder prefix.</param>
 5963        /// <param name="decoderSuffix">Decoder suffix.</param>
 5964        /// <param name="videoCodec">Video codec to use.</param>
 5965        /// <param name="bitDepth">Video color bit depth.</param>
 5966        /// <returns>Hardware decoder name.</returns>
 5967        public string GetHwDecoderName(EncodingOptions options, string decoderPrefix, string decoderSuffix, string video
 5968        {
 05969            if (string.IsNullOrEmpty(decoderPrefix) || string.IsNullOrEmpty(decoderSuffix))
 5970            {
 05971                return null;
 5972            }
 5973
 05974            var decoderName = decoderPrefix + '_' + decoderSuffix;
 5975
 05976            var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains
 05977            if (bitDepth == 10 && isCodecAvailable)
 5978            {
 05979                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 05980                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
 05981                    && !options.EnableDecodingColorDepth10Hevc)
 5982                {
 05983                    return null;
 5984                }
 5985
 05986                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 05987                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 05988                    && !options.EnableDecodingColorDepth10Vp9)
 5989                {
 05990                    return null;
 5991                }
 5992            }
 5993
 05994            if (string.Equals(decoderSuffix, "cuvid", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdec
 5995            {
 05996                return null;
 5997            }
 5998
 05999            if (string.Equals(decoderSuffix, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwD
 6000            {
 06001                return null;
 6002            }
 6003
 06004            if (string.Equals(decoderSuffix, "rkmpp", StringComparison.OrdinalIgnoreCase))
 6005            {
 06006                return null;
 6007            }
 6008
 06009            return isCodecAvailable ? (" -c:v " + decoderName) : null;
 6010        }
 6011
 6012        /// <summary>
 6013        /// Gets a hwaccel type to use as a hardware decoder depending on the system.
 6014        /// </summary>
 6015        /// <param name="state">Encoding state.</param>
 6016        /// <param name="options">Encoding options.</param>
 6017        /// <param name="videoCodec">Video codec to use.</param>
 6018        /// <param name="bitDepth">Video color bit depth.</param>
 6019        /// <param name="outputHwSurface">Specifies if output hw surface.</param>
 6020        /// <returns>Hardware accelerator type.</returns>
 6021        public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, int bitDepth, bo
 6022        {
 06023            var isWindows = OperatingSystem.IsWindows();
 06024            var isLinux = OperatingSystem.IsLinux();
 06025            var isMacOS = OperatingSystem.IsMacOS();
 06026            var isD3d11Supported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va");
 06027            var isVaapiSupported = isLinux && IsVaapiSupported(state);
 06028            var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported();
 06029            var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv");
 06030            var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
 06031            var isRkmppSupported = isLinux && IsRkmppFullSupported();
 06032            var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCas
 06033            var hardwareAccelerationType = options.HardwareAccelerationType;
 6034
 06035            var ffmpegVersion = _mediaEncoder.EncoderVersion;
 6036
 6037            // Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
 06038            var isAv1 = ffmpegVersion < _minFFmpegImplictHwaccel
 06039                && string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
 6040
 6041            // Allow profile mismatch if decoding H.264 baseline with d3d11va and vaapi hwaccels.
 06042            var profileMismatch = string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
 06043                && string.Equals(state.VideoStream?.Profile, "baseline", StringComparison.OrdinalIgnoreCase);
 6044
 6045            // Disable the extra internal copy in nvdec. We already handle it in filter chain.
 06046            var nvdecNoInternalCopy = ffmpegVersion >= _minFFmpegHwaUnsafeOutput;
 6047
 6048            // Strip the display rotation side data from the transposed fmp4 output stream.
 06049            var stripRotationData = (state.VideoStream?.Rotation ?? 0) != 0
 06050                && ffmpegVersion >= _minFFmpegDisplayRotationOption;
 06051            var stripRotationDataArgs = stripRotationData ? " -display_rotation 0" : string.Empty;
 6052
 06053            if (bitDepth == 10 && isCodecAvailable)
 6054            {
 06055                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06056                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
 06057                    && !options.EnableDecodingColorDepth10Hevc)
 6058                {
 06059                    return null;
 6060                }
 6061
 06062                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06063                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06064                    && !options.EnableDecodingColorDepth10Vp9)
 6065                {
 06066                    return null;
 6067                }
 6068            }
 6069
 6070            // Intel qsv/d3d11va/vaapi
 06071            if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 6072            {
 06073                if (options.PreferSystemNativeHwDecoder)
 6074                {
 06075                    if (isVaapiSupported && isCodecAvailable)
 6076                    {
 06077                        return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + st
 06078                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " 
 6079                    }
 6080
 06081                    if (isD3d11Supported && isCodecAvailable)
 6082                    {
 06083                        return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + 
 06084                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 
 6085                    }
 6086                }
 6087                else
 6088                {
 06089                    if (isQsvSupported && isCodecAvailable)
 6090                    {
 06091                        return " -hwaccel qsv" + (outputHwSurface ? " -hwaccel_output_format qsv -noautorotate" + stripR
 6092                    }
 6093                }
 6094            }
 6095
 6096            // Nvidia cuda
 06097            if (hardwareAccelerationType == HardwareAccelerationType.nvenc)
 6098            {
 06099                if (isCudaSupported && isCodecAvailable)
 6100                {
 06101                    if (options.EnableEnhancedNvdecDecoder)
 6102                    {
 6103                        // set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support.
 06104                        return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stri
 06105                            + (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + 
 6106                    }
 6107
 6108                    // cuvid decoder doesn't have threading issue.
 06109                    return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stripRot
 6110                }
 6111            }
 6112
 6113            // Amd d3d11va
 06114            if (hardwareAccelerationType == HardwareAccelerationType.amf)
 6115            {
 06116                if (isD3d11Supported && isCodecAvailable)
 6117                {
 06118                    return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + stri
 06119                        + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v
 6120                }
 6121            }
 6122
 6123            // Vaapi
 06124            if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06125                && isVaapiSupported
 06126                && isCodecAvailable)
 6127            {
 06128                return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + stripRotat
 06129                    + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1
 6130            }
 6131
 6132            // Apple videotoolbox
 06133            if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06134                && isVideotoolboxSupported
 06135                && isCodecAvailable)
 6136            {
 06137                return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string
 6138            }
 6139
 6140            // Rockchip rkmpp
 06141            if (hardwareAccelerationType == HardwareAccelerationType.rkmpp
 06142                && isRkmppSupported
 06143                && isCodecAvailable)
 6144            {
 06145                return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime -noautorotate" + stripR
 6146            }
 6147
 06148            return null;
 6149        }
 6150
 6151        public string GetQsvHwVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6152        {
 06153            var isWindows = OperatingSystem.IsWindows();
 06154            var isLinux = OperatingSystem.IsLinux();
 6155
 06156            if ((!isWindows && !isLinux)
 06157                || options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 6158            {
 06159                return null;
 6160            }
 6161
 06162            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 06163            var isIntelDx11OclSupported = isWindows
 06164                && _mediaEncoder.SupportsHwaccel("d3d11va")
 06165                && isQsvOclSupported;
 06166            var isIntelVaapiOclSupported = isLinux
 06167                && IsVaapiSupported(state)
 06168                && isQsvOclSupported;
 06169            var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
 06170                && _mediaEncoder.SupportsFilter("alphasrc");
 6171
 06172            var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06173                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06174            var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 6175            // TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
 6176
 06177            if (is8bitSwFormatsQsv)
 6178            {
 06179                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06180                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6181                {
 06182                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6183                }
 6184
 06185                if (string.Equals(videoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase))
 6186                {
 06187                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6188                }
 6189
 06190                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6191                {
 06192                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6193                }
 6194
 06195                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6196                {
 06197                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6198                }
 6199            }
 6200
 06201            if (is8_10bitSwFormatsQsv)
 6202            {
 06203                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06204                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6205                {
 06206                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6207                }
 6208
 06209                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6210                {
 06211                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6212                }
 6213
 06214                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 6215                {
 06216                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6217                }
 6218            }
 6219
 06220            return null;
 6221        }
 6222
 6223        public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6224        {
 06225            if ((!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
 06226                || options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 6227            {
 06228                return null;
 6229            }
 6230
 06231            var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
 06232            var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06233                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06234            var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 6235            // TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
 6236
 06237            if (is8bitSwFormatsNvdec)
 6238            {
 06239                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06240                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6241                {
 06242                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6243                }
 6244
 06245                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6246                {
 06247                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6248                }
 6249
 06250                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6251                {
 06252                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6253                }
 6254
 06255                if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6256                {
 06257                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpe
 6258                }
 6259
 06260                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6261                {
 06262                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6263                }
 6264            }
 6265
 06266            if (is8_10bitSwFormatsNvdec)
 6267            {
 06268                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06269                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6270                {
 06271                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6272                }
 6273
 06274                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6275                {
 06276                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6277                }
 6278
 06279                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6280                {
 06281                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6282                }
 6283            }
 6284
 06285            return null;
 6286        }
 6287
 6288        public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitD
 6289        {
 06290            if (!OperatingSystem.IsWindows()
 06291                || options.HardwareAccelerationType != HardwareAccelerationType.amf)
 6292            {
 06293                return null;
 6294            }
 6295
 06296            var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
 06297                && IsOpenclFullSupported()
 06298                && _mediaEncoder.SupportsFilter("alphasrc");
 06299            var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06300                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06301            var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 6302
 06303            if (is8bitSwFormatsAmf)
 6304            {
 06305                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06306                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6307                {
 06308                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6309                }
 6310
 06311                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6312                {
 06313                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6314                }
 6315
 06316                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6317                {
 06318                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6319                }
 6320            }
 6321
 06322            if (is8_10bitSwFormatsAmf)
 6323            {
 06324                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06325                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6326                {
 06327                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6328                }
 6329
 06330                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6331                {
 06332                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6333                }
 6334
 06335                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6336                {
 06337                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6338                }
 6339            }
 6340
 06341            return null;
 6342        }
 6343
 6344        public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6345        {
 06346            if (!OperatingSystem.IsLinux()
 06347                || options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 6348            {
 06349                return null;
 6350            }
 6351
 06352            var hwSurface = IsVaapiSupported(state)
 06353                && IsVaapiFullSupported()
 06354                && IsOpenclFullSupported()
 06355                && _mediaEncoder.SupportsFilter("alphasrc");
 06356            var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06357                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06358            var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 6359
 06360            if (is8bitSwFormatsVaapi)
 6361            {
 06362                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06363                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6364                {
 06365                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6366                }
 6367
 06368                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6369                {
 06370                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6371                }
 6372
 06373                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6374                {
 06375                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6376                }
 6377
 06378                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6379                {
 06380                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6381                }
 6382            }
 6383
 06384            if (is8_10bitSwFormatsVaapi)
 6385            {
 06386                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06387                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6388                {
 06389                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6390                }
 6391
 06392                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6393                {
 06394                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6395                }
 6396
 06397                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6398                {
 06399                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6400                }
 6401            }
 6402
 06403            return null;
 6404        }
 6405
 6406        public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream,
 6407        {
 06408            if (!OperatingSystem.IsMacOS()
 06409                || options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 6410            {
 06411                return null;
 6412            }
 6413
 06414            var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase
 06415                                    || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnore
 06416            var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, String
 6417
 6418            // The related patches make videotoolbox hardware surface working is only available in jellyfin-ffmpeg 7.0.1
 06419            bool useHwSurface = (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface) && IsVideoToolboxFullSupp
 6420
 06421            if (is8bitSwFormatsVt)
 6422            {
 06423                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6424                {
 06425                    return GetHwaccelType(state, options, "vp8", bitDepth, useHwSurface);
 6426                }
 6427            }
 6428
 06429            if (is8_10bitSwFormatsVt)
 6430            {
 06431                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06432                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6433                {
 06434                    return GetHwaccelType(state, options, "h264", bitDepth, useHwSurface);
 6435                }
 6436
 06437                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06438                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6439                {
 06440                    return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
 6441                }
 6442
 06443                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6444                {
 06445                    return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
 6446                }
 6447            }
 6448
 06449            return null;
 6450        }
 6451
 6452        public string GetRkmppVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6453        {
 06454            var isLinux = OperatingSystem.IsLinux();
 6455
 06456            if (!isLinux
 06457                || options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 6458            {
 06459                return null;
 6460            }
 6461
 06462            var inW = state.VideoStream?.Width;
 06463            var inH = state.VideoStream?.Height;
 06464            var reqW = state.BaseRequest.Width;
 06465            var reqH = state.BaseRequest.Height;
 06466            var reqMaxW = state.BaseRequest.MaxWidth;
 06467            var reqMaxH = state.BaseRequest.MaxHeight;
 6468
 6469            // rkrga RGA2e supports range from 1/16 to 16
 06470            if (!IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 16.0f))
 6471            {
 06472                return null;
 6473            }
 6474
 06475            var isRkmppOclSupported = IsRkmppFullSupported() && IsOpenclFullSupported();
 06476            var hwSurface = isRkmppOclSupported
 06477                && _mediaEncoder.SupportsFilter("alphasrc");
 6478
 6479            // rkrga RGA3 supports range from 1/8 to 8
 06480            var isAfbcSupported = hwSurface && IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 6481
 6482            // TODO: add more 8/10bit and 4:2:2 formats for Rkmpp after finishing the ffcheck tool
 06483            var is8bitSwFormatsRkmpp = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06484                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06485            var is10bitSwFormatsRkmpp = string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIg
 06486            var is8_10bitSwFormatsRkmpp = is8bitSwFormatsRkmpp || is10bitSwFormatsRkmpp;
 6487
 6488            // nv15 and nv20 are bit-stream only formats
 06489            if (is10bitSwFormatsRkmpp && !hwSurface)
 6490            {
 06491                return null;
 6492            }
 6493
 06494            if (is8bitSwFormatsRkmpp)
 6495            {
 06496                if (string.Equals(videoStream.Codec, "mpeg1video", StringComparison.OrdinalIgnoreCase))
 6497                {
 06498                    return GetHwaccelType(state, options, "mpeg1video", bitDepth, hwSurface);
 6499                }
 6500
 06501                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6502                {
 06503                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6504                }
 6505
 06506                if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 6507                {
 06508                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
 6509                }
 6510
 06511                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6512                {
 06513                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6514                }
 6515            }
 6516
 06517            if (is8_10bitSwFormatsRkmpp)
 6518            {
 06519                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06520                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6521                {
 06522                    var accelType = GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 06523                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 6524                }
 6525
 06526                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06527                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6528                {
 06529                    var accelType = GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 06530                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 6531                }
 6532
 06533                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6534                {
 06535                    var accelType = GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 06536                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 6537                }
 6538
 06539                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 6540                {
 06541                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6542                }
 6543            }
 6544
 06545            return null;
 6546        }
 6547
 6548        /// <summary>
 6549        /// Gets the number of threads.
 6550        /// </summary>
 6551        /// <param name="state">Encoding state.</param>
 6552        /// <param name="encodingOptions">Encoding options.</param>
 6553        /// <param name="outputVideoCodec">Video codec to use.</param>
 6554        /// <returns>Number of threads.</returns>
 6555#nullable enable
 6556        public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVide
 6557        {
 06558            var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount;
 6559
 06560            if (threads <= 0)
 6561            {
 6562                // Automatically set thread count
 06563                return 0;
 6564            }
 6565
 06566            return Math.Min(threads, Environment.ProcessorCount);
 6567        }
 6568
 6569#nullable disable
 6570        public void TryStreamCopy(EncodingJobInfo state)
 6571        {
 06572            if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream))
 6573            {
 06574                state.OutputVideoCodec = "copy";
 6575            }
 6576            else
 6577            {
 06578                var user = state.User;
 6579
 6580                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 06581                if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
 6582                {
 06583                    state.OutputVideoCodec = "copy";
 6584                }
 6585            }
 6586
 06587            if (state.AudioStream is not null
 06588                && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs))
 6589            {
 06590                state.OutputAudioCodec = "copy";
 6591            }
 6592            else
 6593            {
 06594                var user = state.User;
 6595
 6596                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 06597                if (user is not null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
 6598                {
 06599                    state.OutputAudioCodec = "copy";
 6600                }
 6601            }
 06602        }
 6603
 6604        public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
 6605        {
 06606            var inputModifier = string.Empty;
 06607            var analyzeDurationArgument = string.Empty;
 6608
 6609            // Apply -analyzeduration as per the environment variable,
 6610            // otherwise ffmpeg will break on certain files due to default value is 0.
 06611            var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
 6612
 06613            if (state.MediaSource.AnalyzeDurationMs > 0)
 6614            {
 06615                analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToStr
 6616            }
 06617            else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
 6618            {
 06619                analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
 6620            }
 6621
 06622            if (!string.IsNullOrEmpty(analyzeDurationArgument))
 6623            {
 06624                inputModifier += " " + analyzeDurationArgument;
 6625            }
 6626
 06627            inputModifier = inputModifier.Trim();
 6628
 6629            // Apply -probesize if configured
 06630            var ffmpegProbeSize = _config.GetFFmpegProbeSize();
 6631
 06632            if (!string.IsNullOrEmpty(ffmpegProbeSize))
 6633            {
 06634                inputModifier += $" -probesize {ffmpegProbeSize}";
 6635            }
 6636
 06637            var userAgentParam = GetUserAgentParam(state);
 6638
 06639            if (!string.IsNullOrEmpty(userAgentParam))
 6640            {
 06641                inputModifier += " " + userAgentParam;
 6642            }
 6643
 06644            inputModifier = inputModifier.Trim();
 6645
 06646            var refererParam = GetRefererParam(state);
 6647
 06648            if (!string.IsNullOrEmpty(refererParam))
 6649            {
 06650                inputModifier += " " + refererParam;
 6651            }
 6652
 06653            inputModifier = inputModifier.Trim();
 6654
 06655            inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions, segmentContainer);
 06656            inputModifier = inputModifier.Trim();
 6657
 06658            if (state.InputProtocol == MediaProtocol.Rtsp)
 6659            {
 06660                inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
 6661            }
 6662
 06663            if (!string.IsNullOrEmpty(state.InputAudioSync))
 6664            {
 06665                inputModifier += " -async " + state.InputAudioSync;
 6666            }
 6667
 06668            if (!string.IsNullOrEmpty(state.InputVideoSync))
 6669            {
 06670                inputModifier += " -vsync " + state.InputVideoSync;
 6671            }
 6672
 06673            if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp)
 6674            {
 06675                inputModifier += " -re";
 6676            }
 06677            else if (encodingOptions.EnableSegmentDeletion
 06678                && state.VideoStream is not null
 06679                && state.TranscodingType == TranscodingJobType.Hls
 06680                && IsCopyCodec(state.OutputVideoCodec)
 06681                && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateOption)
 6682            {
 6683                // Set an input read rate limit 10x for using SegmentDeletion with stream-copy
 6684                // to prevent ffmpeg from exiting prematurely (due to fast drive)
 06685                inputModifier += " -readrate 10";
 6686            }
 6687
 06688            var flags = new List<string>();
 06689            if (state.IgnoreInputDts)
 6690            {
 06691                flags.Add("+igndts");
 6692            }
 6693
 06694            if (state.IgnoreInputIndex)
 6695            {
 06696                flags.Add("+ignidx");
 6697            }
 6698
 06699            if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
 6700            {
 06701                flags.Add("+genpts");
 6702            }
 6703
 06704            if (state.DiscardCorruptFramesInput)
 6705            {
 06706                flags.Add("+discardcorrupt");
 6707            }
 6708
 06709            if (state.EnableFastSeekInput)
 6710            {
 06711                flags.Add("+fastseek");
 6712            }
 6713
 06714            if (flags.Count > 0)
 6715            {
 06716                inputModifier += " -fflags " + string.Join(string.Empty, flags);
 6717            }
 6718
 06719            if (state.IsVideoRequest)
 6720            {
 06721                if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && encodingOpt
 6722                {
 06723                    var inputFormat = GetInputFormat(state.InputContainer);
 06724                    if (!string.IsNullOrEmpty(inputFormat))
 6725                    {
 06726                        inputModifier += " -f " + inputFormat;
 6727                    }
 6728                }
 6729            }
 6730
 06731            if (state.MediaSource.RequiresLooping)
 6732            {
 06733                inputModifier += " -stream_loop -1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2";
 6734            }
 6735
 06736            return inputModifier;
 6737        }
 6738
 6739        public void AttachMediaSourceInfo(
 6740            EncodingJobInfo state,
 6741            EncodingOptions encodingOptions,
 6742            MediaSourceInfo mediaSource,
 6743            string requestedUrl)
 6744        {
 06745            ArgumentNullException.ThrowIfNull(state);
 6746
 06747            ArgumentNullException.ThrowIfNull(mediaSource);
 6748
 06749            var path = mediaSource.Path;
 06750            var protocol = mediaSource.Protocol;
 6751
 06752            if (!string.IsNullOrEmpty(mediaSource.EncoderPath) && mediaSource.EncoderProtocol.HasValue)
 6753            {
 06754                path = mediaSource.EncoderPath;
 06755                protocol = mediaSource.EncoderProtocol.Value;
 6756            }
 6757
 06758            state.MediaPath = path;
 06759            state.InputProtocol = protocol;
 06760            state.InputContainer = mediaSource.Container;
 06761            state.RunTimeTicks = mediaSource.RunTimeTicks;
 06762            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 6763
 06764            state.IsoType = mediaSource.IsoType;
 6765
 06766            if (mediaSource.Timestamp.HasValue)
 6767            {
 06768                state.InputTimestamp = mediaSource.Timestamp.Value;
 6769            }
 6770
 06771            state.RunTimeTicks = mediaSource.RunTimeTicks;
 06772            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 06773            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
 6774
 06775            if (state.ReadInputAtNativeFramerate
 06776                || (mediaSource.Protocol == MediaProtocol.File
 06777                && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)))
 6778            {
 06779                state.InputVideoSync = "-1";
 06780                state.InputAudioSync = "1";
 6781            }
 6782
 06783            if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase)
 06784                || string.Equals(mediaSource.Container, "asf", StringComparison.OrdinalIgnoreCase))
 6785            {
 6786                // Seeing some stuttering when transcoding wma to audio-only HLS
 06787                state.InputAudioSync = "1";
 6788            }
 6789
 06790            var mediaStreams = mediaSource.MediaStreams;
 6791
 06792            if (state.IsVideoRequest)
 6793            {
 06794                var videoRequest = state.BaseRequest;
 6795
 06796                if (string.IsNullOrEmpty(videoRequest.VideoCodec))
 6797                {
 06798                    if (string.IsNullOrEmpty(requestedUrl))
 6799                    {
 06800                        requestedUrl = "test." + videoRequest.Container;
 6801                    }
 6802
 06803                    videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
 6804                }
 6805
 06806                state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
 06807                state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Su
 06808                state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
 06809                state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
 6810
 06811                if (state.SubtitleStream is not null && !state.SubtitleStream.IsExternal)
 6812                {
 06813                    state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !
 6814                }
 6815
 06816                EnforceResolutionLimit(state);
 6817
 06818                NormalizeSubtitleEmbed(state);
 6819            }
 6820            else
 6821            {
 06822                state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
 6823            }
 6824
 06825            state.MediaSource = mediaSource;
 6826
 06827            var request = state.BaseRequest;
 06828            var supportedAudioCodecs = state.SupportedAudioCodecs;
 06829            if (request is not null && supportedAudioCodecs is not null && supportedAudioCodecs.Length > 0)
 6830            {
 06831                var supportedAudioCodecsList = supportedAudioCodecs.ToList();
 6832
 06833                ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
 6834
 06835                state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray();
 6836
 06837                request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(_mediaEncoder.CanEncodeToAudioCodec)
 06838                    ?? state.SupportedAudioCodecs.FirstOrDefault();
 6839            }
 6840
 06841            var supportedVideoCodecs = state.SupportedVideoCodecs;
 06842            if (request is not null && supportedVideoCodecs is not null && supportedVideoCodecs.Length > 0)
 6843            {
 06844                var supportedVideoCodecsList = supportedVideoCodecs.ToList();
 6845
 06846                ShiftVideoCodecsIfNeeded(supportedVideoCodecsList, encodingOptions);
 6847
 06848                state.SupportedVideoCodecs = supportedVideoCodecsList.ToArray();
 6849
 06850                request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
 6851            }
 06852        }
 6853
 6854        private void ShiftAudioCodecsIfNeeded(List<string> audioCodecs, MediaStream audioStream)
 6855        {
 6856            // No need to shift if there is only one supported audio codec.
 06857            if (audioCodecs.Count < 2)
 6858            {
 06859                return;
 6860            }
 6861
 06862            var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
 06863            var shiftAudioCodecs = new List<string>();
 06864            if (inputChannels >= 6)
 6865            {
 6866                // DTS and TrueHD are not supported by HLS
 6867                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 06868                shiftAudioCodecs.Add("dca");
 06869                shiftAudioCodecs.Add("truehd");
 6870            }
 6871            else
 6872            {
 6873                // Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
 6874                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 06875                shiftAudioCodecs.Add("ac3");
 06876                shiftAudioCodecs.Add("eac3");
 6877            }
 6878
 06879            if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 6880            {
 06881                return;
 6882            }
 6883
 06884            while (shiftAudioCodecs.Contains(audioCodecs[0], StringComparison.OrdinalIgnoreCase))
 6885            {
 06886                var removed = audioCodecs[0];
 06887                audioCodecs.RemoveAt(0);
 06888                audioCodecs.Add(removed);
 6889            }
 06890        }
 6891
 6892        private void ShiftVideoCodecsIfNeeded(List<string> videoCodecs, EncodingOptions encodingOptions)
 6893        {
 6894            // No need to shift if there is only one supported video codec.
 06895            if (videoCodecs.Count < 2)
 6896            {
 06897                return;
 6898            }
 6899
 6900            // Shift codecs to the end of list if it's not allowed.
 06901            var shiftVideoCodecs = new List<string>();
 06902            if (!encodingOptions.AllowHevcEncoding)
 6903            {
 06904                shiftVideoCodecs.Add("hevc");
 06905                shiftVideoCodecs.Add("h265");
 6906            }
 6907
 06908            if (!encodingOptions.AllowAv1Encoding)
 6909            {
 06910                shiftVideoCodecs.Add("av1");
 6911            }
 6912
 06913            if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 6914            {
 06915                return;
 6916            }
 6917
 06918            while (shiftVideoCodecs.Contains(videoCodecs[0], StringComparison.OrdinalIgnoreCase))
 6919            {
 06920                var removed = videoCodecs[0];
 06921                videoCodecs.RemoveAt(0);
 06922                videoCodecs.Add(removed);
 6923            }
 06924        }
 6925
 6926        private void NormalizeSubtitleEmbed(EncodingJobInfo state)
 6927        {
 06928            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 6929            {
 06930                return;
 6931            }
 6932
 6933            // This is tricky to remux in, after converting to dvdsub it's not positioned correctly
 6934            // Therefore, let's just burn it in
 06935            if (string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 6936            {
 06937                state.SubtitleDeliveryMethod = SubtitleDeliveryMethod.Encode;
 6938            }
 06939        }
 6940
 6941        public string GetSubtitleEmbedArguments(EncodingJobInfo state)
 6942        {
 06943            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 6944            {
 06945                return string.Empty;
 6946            }
 6947
 06948            var format = state.SupportedSubtitleCodecs.FirstOrDefault();
 6949            string codec;
 6950
 06951            if (string.IsNullOrEmpty(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.Ordin
 6952            {
 06953                codec = "copy";
 6954            }
 6955            else
 6956            {
 06957                codec = format;
 6958            }
 6959
 06960            return " -codec:s:0 " + codec + " -disposition:s:0 default";
 6961        }
 6962
 6963        public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, Encoder
 6964        {
 6965            // Get the output codec name
 06966            var videoCodec = GetVideoEncoder(state, encodingOptions);
 6967
 06968            var format = string.Empty;
 06969            var keyFrame = string.Empty;
 06970            var outputPath = state.OutputFilePath;
 6971
 06972            if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase)
 06973                && state.BaseRequest.Context == EncodingContext.Streaming)
 6974            {
 6975                // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
 06976                format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov";
 6977            }
 6978
 06979            var threads = GetNumberOfThreads(state, encodingOptions, videoCodec);
 6980
 06981            var inputModifier = GetInputModifier(state, encodingOptions, null);
 6982
 06983            return string.Format(
 06984                CultureInfo.InvariantCulture,
 06985                "{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
 06986                inputModifier,
 06987                GetInputArgument(state, encodingOptions, null),
 06988                keyFrame,
 06989                GetMapArgs(state),
 06990                GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset),
 06991                threads,
 06992                GetProgressiveVideoAudioArguments(state, encodingOptions),
 06993                GetSubtitleEmbedArguments(state),
 06994                format,
 06995                outputPath).Trim();
 6996        }
 6997
 6998        public string GetOutputFFlags(EncodingJobInfo state)
 6999        {
 07000            var flags = new List<string>();
 07001            if (state.GenPtsOutput)
 7002            {
 07003                flags.Add("+genpts");
 7004            }
 7005
 07006            if (flags.Count > 0)
 7007            {
 07008                return " -fflags " + string.Join(string.Empty, flags);
 7009            }
 7010
 07011            return string.Empty;
 7012        }
 7013
 7014        public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoC
 7015        {
 07016            var args = "-codec:v:0 " + videoCodec;
 7017
 07018            if (state.BaseRequest.EnableMpegtsM2TsMode)
 7019            {
 07020                args += " -mpegts_m2ts_mode 1";
 7021            }
 7022
 07023            if (IsCopyCodec(videoCodec))
 7024            {
 07025                if (state.VideoStream is not null
 07026                    && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
 07027                    && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
 7028                {
 07029                    string bitStreamArgs = GetBitStreamArgs(state.VideoStream);
 07030                    if (!string.IsNullOrEmpty(bitStreamArgs))
 7031                    {
 07032                        args += " " + bitStreamArgs;
 7033                    }
 7034                }
 7035
 07036                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7037                {
 07038                    args += " -copyts -avoid_negative_ts disabled -start_at_zero";
 7039                }
 7040
 07041                if (!state.RunTimeTicks.HasValue)
 7042                {
 07043                    args += " -fflags +genpts";
 7044                }
 7045            }
 7046            else
 7047            {
 07048                var keyFrameArg = string.Format(
 07049                    CultureInfo.InvariantCulture,
 07050                    " -force_key_frames \"expr:gte(t,n_forced*{0})\"",
 07051                    5);
 7052
 07053                args += keyFrameArg;
 7054
 07055                var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream &&
 7056
 07057                var hasCopyTs = false;
 7058
 7059                // video processing filters.
 07060                var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
 7061
 07062                var negativeMapArgs = GetNegativeMapArgsByFilters(state, videoProcessParam);
 7063
 07064                args = negativeMapArgs + args + videoProcessParam;
 7065
 07066                hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
 7067
 07068                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7069                {
 07070                    if (!hasCopyTs)
 7071                    {
 07072                        args += " -copyts";
 7073                    }
 7074
 07075                    args += " -avoid_negative_ts disabled";
 7076
 07077                    if (!(state.SubtitleStream is not null && state.SubtitleStream.IsExternal && !state.SubtitleStream.I
 7078                    {
 07079                        args += " -start_at_zero";
 7080                    }
 7081                }
 7082
 07083                var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset);
 7084
 07085                if (!string.IsNullOrEmpty(qualityParam))
 7086                {
 07087                    args += " " + qualityParam.Trim();
 7088                }
 7089            }
 7090
 07091            if (!string.IsNullOrEmpty(state.OutputVideoSync))
 7092            {
 07093                args += " -vsync " + state.OutputVideoSync;
 7094            }
 7095
 07096            args += GetOutputFFlags(state);
 7097
 07098            return args;
 7099        }
 7100
 7101        public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions)
 7102        {
 7103            // If the video doesn't have an audio stream, return a default.
 07104            if (state.AudioStream is null && state.VideoStream is not null)
 7105            {
 07106                return string.Empty;
 7107            }
 7108
 7109            // Get the output codec name
 07110            var codec = GetAudioEncoder(state);
 7111
 07112            var args = "-codec:a:0 " + codec;
 7113
 07114            if (IsCopyCodec(codec))
 7115            {
 07116                return args;
 7117            }
 7118
 07119            var channels = state.OutputAudioChannels;
 7120
 07121            var useDownMixAlgorithm = state.AudioStream is not null
 07122                                      && DownMixAlgorithmsHelper.AlgorithmFilterStrings.ContainsKey((encodingOptions.Dow
 7123
 07124            if (channels.HasValue && !useDownMixAlgorithm)
 7125            {
 07126                args += " -ac " + channels.Value;
 7127            }
 7128
 07129            var bitrate = state.OutputAudioBitrate;
 07130            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
 7131            {
 07132                var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2);
 07133                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7134                {
 07135                    args += vbrParam;
 7136                }
 7137                else
 7138                {
 07139                    args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
 7140                }
 7141            }
 7142
 07143            if (state.OutputAudioSampleRate.HasValue)
 7144            {
 07145                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
 7146            }
 7147
 07148            args += GetAudioFilterParam(state, encodingOptions);
 7149
 07150            return args;
 7151        }
 7152
 7153        public string GetProgressiveAudioFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string 
 7154        {
 07155            var audioTranscodeParams = new List<string>();
 7156
 07157            var bitrate = state.OutputAudioBitrate;
 07158            var channels = state.OutputAudioChannels;
 07159            var outputCodec = state.OutputAudioCodec;
 7160
 07161            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
 7162            {
 07163                var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2);
 07164                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7165                {
 07166                    audioTranscodeParams.Add(vbrParam);
 7167                }
 7168                else
 7169                {
 07170                    audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
 7171                }
 7172            }
 7173
 07174            if (channels.HasValue)
 7175            {
 07176                audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)
 7177            }
 7178
 07179            if (!string.IsNullOrEmpty(outputCodec))
 7180            {
 07181                audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
 7182            }
 7183
 07184            if (GetAudioEncoder(state).StartsWith("pcm_", StringComparison.Ordinal))
 7185            {
 07186                audioTranscodeParams.Add(string.Concat("-f ", GetAudioEncoder(state).AsSpan(4)));
 07187                audioTranscodeParams.Add("-ar " + state.BaseRequest.AudioBitRate);
 7188            }
 7189
 07190            if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
 7191            {
 7192                // opus only supports specific sampling rates
 07193                var sampleRate = state.OutputAudioSampleRate;
 07194                if (sampleRate.HasValue)
 7195                {
 07196                    var sampleRateValue = sampleRate.Value switch
 07197                    {
 07198                        <= 8000 => 8000,
 07199                        <= 12000 => 12000,
 07200                        <= 16000 => 16000,
 07201                        <= 24000 => 24000,
 07202                        _ => 48000
 07203                    };
 7204
 07205                    audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
 7206                }
 7207            }
 7208
 7209            // Copy the movflags from GetProgressiveVideoFullCommandLine
 7210            // See #9248 and the associated PR for why this is needed
 07211            if (_mp4ContainerNames.Contains(state.OutputContainer))
 7212            {
 07213                audioTranscodeParams.Add("-movflags empty_moov+delay_moov");
 7214            }
 7215
 07216            var threads = GetNumberOfThreads(state, encodingOptions, null);
 7217
 07218            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7219
 07220            return string.Format(
 07221                CultureInfo.InvariantCulture,
 07222                "{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"",
 07223                inputModifier,
 07224                GetInputArgument(state, encodingOptions, null),
 07225                threads,
 07226                " -vn",
 07227                string.Join(' ', audioTranscodeParams),
 07228                outputPath,
 07229                string.Empty,
 07230                string.Empty,
 07231                string.Empty).Trim();
 7232        }
 7233
 7234        public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
 7235        {
 07236            var index = 0;
 07237            var length = mediaStreams.Count;
 7238
 07239            for (var i = 0; i < length; i++)
 7240            {
 07241                var currentMediaStream = mediaStreams[i];
 07242                if (currentMediaStream == streamToFind)
 7243                {
 07244                    return index;
 7245                }
 7246
 07247                if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
 7248                {
 07249                    index++;
 7250                }
 7251            }
 7252
 07253            return -1;
 7254        }
 7255
 7256        public static bool IsCopyCodec(string codec)
 7257        {
 07258            return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
 7259        }
 7260    }
 7261}

Methods/Properties

.ctor(MediaBrowser.Common.Configuration.IApplicationPaths,MediaBrowser.Controller.MediaEncoding.IMediaEncoder,MediaBrowser.Controller.MediaEncoding.ISubtitleEncoder,Microsoft.Extensions.Configuration.IConfiguration,MediaBrowser.Common.Configuration.IConfigurationManager)
.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)
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)
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)
IsAAC(MediaBrowser.Model.Entities.MediaStream)
GetBitStreamArgs(MediaBrowser.Model.Entities.MediaStream)
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)
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>)
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)