< 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: 24
Uncovered lines: 3583
Coverable lines: 3607
Total lines: 7513
Line coverage: 0.6%
Branch coverage
0%
Covered branches: 0
Total branches: 3555
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.cctor()100%210%
GetH264Encoder(...)100%210%
GetH265Encoder(...)100%210%
GetAv1Encoder(...)100%210%
GetH26xOrAv1Encoder(...)0%110100%
GetMjpegEncoder(...)0%272160%
IsVaapiSupported(...)0%2040%
IsVaapiFullSupported()0%272160%
IsRkmppFullSupported()0%4260%
IsOpenclFullSupported()0%4260%
IsCudaFullSupported()0%110100%
IsVulkanFullSupported()0%110100%
IsVideoToolboxFullSupported()0%7280%
IsSwTonemapAvailable(...)0%4260%
IsHwTonemapAvailable(...)0%420200%
IsVulkanHwTonemapAvailable(...)0%4260%
IsIntelVppTonemapAvailable(...)0%272160%
IsVideoToolboxTonemapAvailable(...)0%272160%
IsVideoStreamHevcRext(...)0%342180%
GetVideoEncoder(...)0%210140%
GetUserAgentParam(...)0%620%
GetRefererParam(...)0%620%
GetInputFormat(...)0%1482380%
GetDecoderFromCodec(...)0%7280%
InferAudioCodec(...)0%3422580%
InferVideoCodec(...)0%156120%
GetVideoProfileScore(...)0%4260%
GetAudioEncoder(...)0%420200%
GetRkmppDeviceArgs(...)0%620%
GetVideoToolboxDeviceArgs(...)0%620%
GetCudaDeviceArgs(...)0%2040%
GetVulkanDeviceArgs(...)0%7280%
GetOpenclDeviceArgs(...)0%7280%
GetD3d11vaDeviceArgs(...)0%4260%
GetVaapiDeviceArgs(...)0%272160%
GetDrmDeviceArgs(...)0%2040%
GetQsvDeviceArgs(...)0%7280%
GetFilterHwDeviceArgs(...)0%620%
GetGraphicalSubCanvasSize(...)0%420200%
GetInputVideoHwaccelArgs(...)0%145201200%
GetInputArgument(...)0%1190340%
IsH264(...)0%2040%
IsH265(...)0%2040%
IsAAC(...)0%620%
GetBitStreamArgs(...)0%4260%
GetAudioBitStreamArguments(...)0%110100%
GetSegmentFileExtension(...)0%620%
GetVideoBitrateParam(...)0%812280%
GetEncoderParam(...)0%8010890%
NormalizeTranscodingLevel(...)0%506220%
GetTextSubtitlesFilter(...)0%210140%
GetFramerateParam(...)0%156120%
GetHlsVideoKeyFrameArguments(...)0%1806420%
GetVideoQualityParam(...)0%325801800%
CanStreamCopyVideo(...)0%126561120%
CanStreamCopyAudio(...)0%1190340%
GetVideoBitrateParamValue(...)0%506220%
GetMinBitrate(...)0%2040%
GetVideoBitrateScaleFactor(...)0%7280%
ScaleBitrate(...)0%110100%
GetAudioBitrateParam(...)100%210%
GetAudioBitrateParam(...)0%2162460%
GetAudioVbrModeParam(...)0%1190340%
GetAudioFilterParam(...)0%702260%
GetNumAudioChannelsParam(...)0%930300%
EnforceResolutionLimit(...)0%2040%
GetFastSeekCommandLineParameter(...)0%506220%
GetMapArgs(...)0%1640400%
GetNegativeMapArgsByFilters(...)0%2040%
GetMediaStream(...)0%156120%
GetFixedOutputSize(...)0%272160%
IsScaleRatioSupported(...)0%506220%
GetHwScaleFilter(...)0%600240%
GetGraphicalSubPreProcessFilters(...)0%342180%
GetAlphaSrcFilter(...)0%4260%
GetSwScaleFilter(...)0%702260%
GetFixedSwScaleFilter(...)0%132110%
GetSwDeinterlaceFilter(...)0%4260%
GetHwDeinterlaceFilter(...)0%702260%
GetHwTonemapFilter(...)0%1332360%
GetLibplaceboFilter(...)0%506220%
GetVideoTransposeDirection(...)0%156120%
GetSwVidFilterChain(...)0%2550500%
GetNvidiaVidFilterChain(...)0%110100%
GetNvidiaVidFiltersPrefered(...)0%7482860%
GetAmdVidFilterChain(...)0%210140%
GetAmdDx11VidFiltersPrefered(...)0%7140840%
GetIntelVidFilterChain(...)0%506220%
GetIntelQsvDx11VidFiltersPrefered(...)0%203061420%
GetIntelQsvVaapiVidFiltersPrefered(...)0%175561320%
GetVaapiVidFilterChain(...)0%1190340%
GetIntelVaapiFullVidFiltersPrefered(...)0%126561120%
GetAmdVaapiFullVidFiltersPrefered(...)0%101001000%
GetVaapiLimitedVidFiltersPrefered(...)0%8190900%
GetAppleVidFilterChain(...)0%156120%
GetAppleVidFiltersPreferred(...)0%5550740%
GetRkmppVidFilterChain(...)0%272160%
GetRkmppVidFiltersPrefered(...)0%131101140%
GetVideoProcessingFilterParam(...)0%2756520%
GetOverwriteColorPropertiesParam(...)0%2040%
GetInputHdrParam(...)0%620%
GetOutputSdrParam(...)0%2040%
GetVideoColorBitDepth(...)0%600240%
GetHardwareVideoDecoder(...)0%2352480%
GetHwDecoderName(...)0%1190340%
GetHwaccelType(...)0%150061220%
GetQsvHwVidDecoder(...)0%3192560%
GetNvdecVidDecoder(...)0%2162460%
GetAmfVidDecoder(...)0%1056320%
GetVaapiVidDecoder(...)0%2756520%
GetVideotoolboxVidDecoder(...)0%2352480%
GetRkmppVidDecoder(...)0%2756520%
GetNumberOfThreads(...)0%4260%
TryStreamCopy(...)0%272160%
GetInputModifier(...)0%3660600%
AttachMediaSourceInfo(...)0%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%
ShouldEncodeSubtitle(...)0%2040%
GetVideoSyncOption(...)0%132110%

File(s)

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

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CS1591
 4// We need lowercase normalized string for ffmpeg
 5#pragma warning disable CA1308
 6
 7using System;
 8using System.Collections.Generic;
 9using System.Globalization;
 10using System.IO;
 11using System.Linq;
 12using System.Runtime.InteropServices;
 13using System.Text;
 14using System.Text.RegularExpressions;
 15using System.Threading;
 16using Jellyfin.Data;
 17using Jellyfin.Data.Enums;
 18using Jellyfin.Database.Implementations.Enums;
 19using Jellyfin.Extensions;
 20using MediaBrowser.Common.Configuration;
 21using MediaBrowser.Controller.Extensions;
 22using MediaBrowser.Model.Configuration;
 23using MediaBrowser.Model.Dlna;
 24using MediaBrowser.Model.Dto;
 25using MediaBrowser.Model.Entities;
 26using MediaBrowser.Model.MediaInfo;
 27using Microsoft.Extensions.Configuration;
 28using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
 29
 30namespace MediaBrowser.Controller.MediaEncoding
 31{
 32    public partial class EncodingHelper
 33    {
 34        /// <summary>
 35        /// The codec validation regex.
 36        /// This regular expression matches strings that consist of alphanumeric characters, hyphens,
 37        /// periods, underscores, commas, and vertical bars, with a length between 0 and 40 characters.
 38        /// This should matches all common valid codecs.
 39        /// </summary>
 40        public const string ValidationRegex = @"^[a-zA-Z0-9\-\._,|]{0,40}$";
 41
 42        private const string _defaultMjpegEncoder = "mjpeg";
 43
 44        private const string QsvAlias = "qs";
 45        private const string VaapiAlias = "va";
 46        private const string D3d11vaAlias = "dx11";
 47        private const string VideotoolboxAlias = "vt";
 48        private const string RkmppAlias = "rk";
 49        private const string OpenclAlias = "ocl";
 50        private const string CudaAlias = "cu";
 51        private const string DrmAlias = "dr";
 52        private const string VulkanAlias = "vk";
 53        private readonly IApplicationPaths _appPaths;
 54        private readonly IMediaEncoder _mediaEncoder;
 55        private readonly ISubtitleEncoder _subtitleEncoder;
 56        private readonly IConfiguration _config;
 57        private readonly IConfigurationManager _configurationManager;
 58
 59        // i915 hang was fixed by linux 6.2 (3f882f2)
 2160        private readonly Version _minKerneli915Hang = new Version(5, 18);
 2161        private readonly Version _maxKerneli915Hang = new Version(6, 1, 3);
 2162        private readonly Version _minFixedKernel60i915Hang = new Version(6, 0, 18);
 2163        private readonly Version _minKernelVersionAmdVkFmtModifier = new Version(5, 15);
 64
 2165        private readonly Version _minFFmpegImplicitHwaccel = new Version(6, 0);
 2166        private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0);
 2167        private readonly Version _minFFmpegOclCuTonemapMode = new Version(5, 1, 3);
 2168        private readonly Version _minFFmpegSvtAv1Params = new Version(5, 1);
 2169        private readonly Version _minFFmpegVaapiH26xEncA53CcSei = new Version(6, 0);
 2170        private readonly Version _minFFmpegReadrateOption = new Version(5, 0);
 2171        private readonly Version _minFFmpegWorkingVtHwSurface = new Version(7, 0, 1);
 2172        private readonly Version _minFFmpegDisplayRotationOption = new Version(6, 0);
 2173        private readonly Version _minFFmpegAdvancedTonemapMode = new Version(7, 0, 1);
 2174        private readonly Version _minFFmpegAlteredVaVkInterop = new Version(7, 0, 1);
 2175        private readonly Version _minFFmpegQsvVppTonemapOption = new Version(7, 0, 1);
 2176        private readonly Version _minFFmpegQsvVppOutRangeOption = new Version(7, 0, 1);
 2177        private readonly Version _minFFmpegVaapiDeviceVendorId = new Version(7, 0, 1);
 2178        private readonly Version _minFFmpegQsvVppScaleModeOption = new Version(6, 0);
 79
 080        private static readonly Regex _validationRegex = new(ValidationRegex, RegexOptions.Compiled);
 81
 082        private static readonly string[] _videoProfilesH264 =
 083        [
 084            "ConstrainedBaseline",
 085            "Baseline",
 086            "Extended",
 087            "Main",
 088            "High",
 089            "ProgressiveHigh",
 090            "ConstrainedHigh",
 091            "High10"
 092        ];
 93
 094        private static readonly string[] _videoProfilesH265 =
 095        [
 096            "Main",
 097            "Main10"
 098        ];
 99
 0100        private static readonly string[] _videoProfilesAv1 =
 0101        [
 0102            "Main",
 0103            "High",
 0104            "Professional",
 0105        ];
 106
 0107        private static readonly HashSet<string> _mp4ContainerNames = new(StringComparer.OrdinalIgnoreCase)
 0108        {
 0109            "mp4",
 0110            "m4a",
 0111            "m4p",
 0112            "m4b",
 0113            "m4r",
 0114            "m4v",
 0115        };
 116
 0117        private static readonly TonemappingMode[] _legacyTonemapModes = [TonemappingMode.max, TonemappingMode.rgb];
 0118        private static readonly TonemappingMode[] _advancedTonemapModes = [TonemappingMode.lum, TonemappingMode.itp];
 119
 120        // Set max transcoding channels for encoders that can't handle more than a set amount of channels
 121        // AAC, FLAC, ALAC, libopus, libvorbis encoders all support at least 8 channels
 0122        private static readonly Dictionary<string, int> _audioTranscodeChannelLookup = new(StringComparer.OrdinalIgnoreC
 0123        {
 0124            { "libmp3lame", 2 },
 0125            { "libfdk_aac", 6 },
 0126            { "ac3", 6 },
 0127            { "eac3", 6 },
 0128            { "dca", 6 },
 0129            { "mlp", 6 },
 0130            { "truehd", 6 },
 0131        };
 132
 0133        private static readonly Dictionary<HardwareAccelerationType, string> _mjpegCodecMap = new()
 0134        {
 0135            { HardwareAccelerationType.vaapi, _defaultMjpegEncoder + "_vaapi" },
 0136            { HardwareAccelerationType.qsv, _defaultMjpegEncoder + "_qsv" },
 0137            { HardwareAccelerationType.videotoolbox, _defaultMjpegEncoder + "_videotoolbox" },
 0138            { HardwareAccelerationType.rkmpp, _defaultMjpegEncoder + "_rkmpp" }
 0139        };
 140
 0141        public static readonly string[] LosslessAudioCodecs =
 0142        [
 0143            "alac",
 0144            "ape",
 0145            "flac",
 0146            "mlp",
 0147            "truehd",
 0148            "wavpack"
 0149        ];
 150
 151        public EncodingHelper(
 152            IApplicationPaths appPaths,
 153            IMediaEncoder mediaEncoder,
 154            ISubtitleEncoder subtitleEncoder,
 155            IConfiguration config,
 156            IConfigurationManager configurationManager)
 157        {
 21158            _appPaths = appPaths;
 21159            _mediaEncoder = mediaEncoder;
 21160            _subtitleEncoder = subtitleEncoder;
 21161            _config = config;
 21162            _configurationManager = configurationManager;
 21163        }
 164
 165        [GeneratedRegex(@"\s+")]
 166        private static partial Regex WhiteSpaceRegex();
 167
 168        public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0169            => GetH26xOrAv1Encoder("libx264", "h264", state, encodingOptions);
 170
 171        public string GetH265Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0172            => GetH26xOrAv1Encoder("libx265", "hevc", state, encodingOptions);
 173
 174        public string GetAv1Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0175            => GetH26xOrAv1Encoder("libsvtav1", "av1", state, encodingOptions);
 176
 177        private string GetH26xOrAv1Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptio
 178        {
 179            // Only use alternative encoders for video files.
 180            // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying
 181            // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such 
 0182            if (state.VideoType == VideoType.VideoFile)
 183            {
 0184                var hwType = encodingOptions.HardwareAccelerationType;
 185
 0186                var codecMap = new Dictionary<HardwareAccelerationType, string>()
 0187                {
 0188                    { HardwareAccelerationType.amf,                  hwEncoder + "_amf" },
 0189                    { HardwareAccelerationType.nvenc,                hwEncoder + "_nvenc" },
 0190                    { HardwareAccelerationType.qsv,                  hwEncoder + "_qsv" },
 0191                    { HardwareAccelerationType.vaapi,                hwEncoder + "_vaapi" },
 0192                    { HardwareAccelerationType.videotoolbox,         hwEncoder + "_videotoolbox" },
 0193                    { HardwareAccelerationType.v4l2m2m,              hwEncoder + "_v4l2m2m" },
 0194                    { HardwareAccelerationType.rkmpp,                hwEncoder + "_rkmpp" },
 0195                };
 196
 0197                if (hwType != HardwareAccelerationType.none
 0198                    && encodingOptions.EnableHardwareEncoding
 0199                    && codecMap.TryGetValue(hwType, out var preferredEncoder)
 0200                    && _mediaEncoder.SupportsEncoder(preferredEncoder))
 201                {
 0202                    return preferredEncoder;
 203                }
 204            }
 205
 0206            return defaultEncoder;
 207        }
 208
 209        private string GetMjpegEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 210        {
 0211            if (state.VideoType == VideoType.VideoFile)
 212            {
 0213                var hwType = encodingOptions.HardwareAccelerationType;
 214
 215                // Only Intel has VA-API MJPEG encoder
 0216                if (hwType == HardwareAccelerationType.vaapi
 0217                    && !(_mediaEncoder.IsVaapiDeviceInteliHD
 0218                         || _mediaEncoder.IsVaapiDeviceInteli965))
 219                {
 0220                    return _defaultMjpegEncoder;
 221                }
 222
 0223                if (hwType != HardwareAccelerationType.none
 0224                    && encodingOptions.EnableHardwareEncoding
 0225                    && _mjpegCodecMap.TryGetValue(hwType, out var preferredEncoder)
 0226                    && _mediaEncoder.SupportsEncoder(preferredEncoder))
 227                {
 0228                    return preferredEncoder;
 229                }
 230            }
 231
 0232            return _defaultMjpegEncoder;
 233        }
 234
 235        private bool IsVaapiSupported(EncodingJobInfo state)
 236        {
 237            // vaapi will throw an error with this input
 238            // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99.
 0239            if (string.Equals(state.VideoStream?.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 240            {
 0241                return false;
 242            }
 243
 0244            return _mediaEncoder.SupportsHwaccel("vaapi");
 245        }
 246
 247        private bool IsVaapiFullSupported()
 248        {
 0249            return _mediaEncoder.SupportsHwaccel("drm")
 0250                   && _mediaEncoder.SupportsHwaccel("vaapi")
 0251                   && _mediaEncoder.SupportsFilter("scale_vaapi")
 0252                   && _mediaEncoder.SupportsFilter("deinterlace_vaapi")
 0253                   && _mediaEncoder.SupportsFilter("tonemap_vaapi")
 0254                   && _mediaEncoder.SupportsFilter("procamp_vaapi")
 0255                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVaapiFrameSync)
 0256                   && _mediaEncoder.SupportsFilter("transpose_vaapi")
 0257                   && _mediaEncoder.SupportsFilter("hwupload_vaapi");
 258        }
 259
 260        private bool IsRkmppFullSupported()
 261        {
 0262            return _mediaEncoder.SupportsHwaccel("rkmpp")
 0263                   && _mediaEncoder.SupportsFilter("scale_rkrga")
 0264                   && _mediaEncoder.SupportsFilter("vpp_rkrga")
 0265                   && _mediaEncoder.SupportsFilter("overlay_rkrga");
 266        }
 267
 268        private bool IsOpenclFullSupported()
 269        {
 0270            return _mediaEncoder.SupportsHwaccel("opencl")
 0271                   && _mediaEncoder.SupportsFilter("scale_opencl")
 0272                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390)
 0273                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclFrameSync);
 274
 275            // Let transpose_opencl optional for the time being.
 276        }
 277
 278        private bool IsCudaFullSupported()
 279        {
 0280            return _mediaEncoder.SupportsHwaccel("cuda")
 0281                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat)
 0282                   && _mediaEncoder.SupportsFilter("yadif_cuda")
 0283                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName)
 0284                   && _mediaEncoder.SupportsFilter("overlay_cuda")
 0285                   && _mediaEncoder.SupportsFilter("hwupload_cuda");
 286
 287            // Let transpose_cuda optional for the time being.
 288        }
 289
 290        private bool IsVulkanFullSupported()
 291        {
 0292            return _mediaEncoder.SupportsHwaccel("vulkan")
 0293                   && _mediaEncoder.SupportsFilter("libplacebo")
 0294                   && _mediaEncoder.SupportsFilter("scale_vulkan")
 0295                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVulkanFrameSync)
 0296                   && _mediaEncoder.SupportsFilter("transpose_vulkan")
 0297                   && _mediaEncoder.SupportsFilter("flip_vulkan");
 298        }
 299
 300        private bool IsVideoToolboxFullSupported()
 301        {
 0302            return _mediaEncoder.SupportsHwaccel("videotoolbox")
 0303                && _mediaEncoder.SupportsFilter("yadif_videotoolbox")
 0304                && _mediaEncoder.SupportsFilter("overlay_videotoolbox")
 0305                && _mediaEncoder.SupportsFilter("tonemap_videotoolbox")
 0306                && _mediaEncoder.SupportsFilter("scale_vt");
 307
 308            // Let transpose_vt optional for the time being.
 309        }
 310
 311        private bool IsSwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 312        {
 0313            if (state.VideoStream is null
 0314                || GetVideoColorBitDepth(state) < 10
 0315                || !_mediaEncoder.SupportsFilter("tonemapx"))
 316            {
 0317                return false;
 318            }
 319
 0320            return state.VideoStream.VideoRange == VideoRange.HDR;
 321        }
 322
 323        private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 324        {
 0325            if (state.VideoStream is null
 0326                || !options.EnableTonemapping
 0327                || GetVideoColorBitDepth(state) < 10)
 328            {
 0329                return false;
 330            }
 331
 0332            if (state.VideoStream.VideoRange == VideoRange.HDR
 0333                && state.VideoStream.VideoRangeType == VideoRangeType.DOVI)
 334            {
 335                // Only native SW decoder and HW accelerator can parse dovi rpu.
 0336                var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 0337                var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 0338                var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 0339                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 0340                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 0341                var isVideoToolBoxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 0342                return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder || isVideoToolBoxDecoder;
 343            }
 344
 0345            return state.VideoStream.VideoRange == VideoRange.HDR
 0346                   && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
 0347                       || state.VideoStream.VideoRangeType == VideoRangeType.HLG
 0348                       || state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10
 0349                       || state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHLG);
 350        }
 351
 352        private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 353        {
 0354            if (state.VideoStream is null)
 355            {
 0356                return false;
 357            }
 358
 359            // libplacebo has partial Dolby Vision to SDR tonemapping support.
 0360            return options.EnableTonemapping
 0361                   && state.VideoStream.VideoRange == VideoRange.HDR
 0362                   && GetVideoColorBitDepth(state) == 10;
 363        }
 364
 365        private bool IsIntelVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 366        {
 0367            if (state.VideoStream is null
 0368                || !options.EnableVppTonemapping
 0369                || GetVideoColorBitDepth(state) < 10)
 370            {
 0371                return false;
 372            }
 373
 374            // prefer 'tonemap_vaapi' over 'vpp_qsv' on Linux for supporting Gen9/KBLx.
 375            // 'vpp_qsv' requires VPL, which is only supported on Gen12/TGLx and newer.
 0376            if (OperatingSystem.IsWindows()
 0377                && options.HardwareAccelerationType == HardwareAccelerationType.qsv
 0378                && _mediaEncoder.EncoderVersion < _minFFmpegQsvVppTonemapOption)
 379            {
 0380                return false;
 381            }
 382
 0383            return state.VideoStream.VideoRange == VideoRange.HDR
 0384                   && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
 0385                       || state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10);
 386        }
 387
 388        private bool IsVideoToolboxTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 389        {
 0390            if (state.VideoStream is null
 0391                || !options.EnableVideoToolboxTonemapping
 0392                || GetVideoColorBitDepth(state) < 10)
 393            {
 0394                return false;
 395            }
 396
 397            // Certain DV profile 5 video works in Safari with direct playing, but the VideoToolBox does not produce cor
 398            // All other HDR formats working.
 0399            return state.VideoStream.VideoRange == VideoRange.HDR
 0400                   && state.VideoStream.VideoRangeType is VideoRangeType.HDR10 or VideoRangeType.HLG or VideoRangeType.H
 401        }
 402
 403        private bool IsVideoStreamHevcRext(EncodingJobInfo state)
 404        {
 0405            var videoStream = state.VideoStream;
 0406            if (videoStream is null)
 407            {
 0408                return false;
 409            }
 410
 0411            return string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 0412                   && (string.Equals(videoStream.Profile, "Rext", StringComparison.OrdinalIgnoreCase)
 0413                       || string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 0414                       || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 0415                       || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 0416                       || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 0417                       || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase)
 0418                       || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)
 0419                       || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase));
 420        }
 421
 422        /// <summary>
 423        /// Gets the name of the output video codec.
 424        /// </summary>
 425        /// <param name="state">Encoding state.</param>
 426        /// <param name="encodingOptions">Encoding options.</param>
 427        /// <returns>Encoder string.</returns>
 428        public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 429        {
 0430            var codec = state.OutputVideoCodec;
 431
 0432            if (!string.IsNullOrEmpty(codec))
 433            {
 0434                if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 435                {
 0436                    return GetAv1Encoder(state, encodingOptions);
 437                }
 438
 0439                if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 0440                    || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
 441                {
 0442                    return GetH265Encoder(state, encodingOptions);
 443                }
 444
 0445                if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
 446                {
 0447                    return GetH264Encoder(state, encodingOptions);
 448                }
 449
 0450                if (string.Equals(codec, "mjpeg", StringComparison.OrdinalIgnoreCase))
 451                {
 0452                    return GetMjpegEncoder(state, encodingOptions);
 453                }
 454
 0455                if (_validationRegex.IsMatch(codec))
 456                {
 0457                    return codec.ToLowerInvariant();
 458                }
 459            }
 460
 0461            return "copy";
 462        }
 463
 464        /// <summary>
 465        /// Gets the user agent param.
 466        /// </summary>
 467        /// <param name="state">The state.</param>
 468        /// <returns>System.String.</returns>
 469        public string GetUserAgentParam(EncodingJobInfo state)
 470        {
 0471            if (state.RemoteHttpHeaders.TryGetValue("User-Agent", out string useragent))
 472            {
 0473                return "-user_agent \"" + useragent + "\"";
 474            }
 475
 0476            return string.Empty;
 477        }
 478
 479        /// <summary>
 480        /// Gets the referer param.
 481        /// </summary>
 482        /// <param name="state">The state.</param>
 483        /// <returns>System.String.</returns>
 484        public string GetRefererParam(EncodingJobInfo state)
 485        {
 0486            if (state.RemoteHttpHeaders.TryGetValue("Referer", out string referer))
 487            {
 0488                return "-referer \"" + referer + "\"";
 489            }
 490
 0491            return string.Empty;
 492        }
 493
 494        public static string GetInputFormat(string container)
 495        {
 0496            if (string.IsNullOrEmpty(container) || !_validationRegex.IsMatch(container))
 497            {
 0498                return null;
 499            }
 500
 0501            container = container.Replace("mkv", "matroska", StringComparison.OrdinalIgnoreCase);
 502
 0503            if (string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
 504            {
 0505                return "mpegts";
 506            }
 507
 508            // For these need to find out the ffmpeg names
 0509            if (string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase))
 510            {
 0511                return null;
 512            }
 513
 0514            if (string.Equals(container, "wmv", StringComparison.OrdinalIgnoreCase))
 515            {
 0516                return null;
 517            }
 518
 0519            if (string.Equals(container, "mts", StringComparison.OrdinalIgnoreCase))
 520            {
 0521                return null;
 522            }
 523
 0524            if (string.Equals(container, "vob", StringComparison.OrdinalIgnoreCase))
 525            {
 0526                return null;
 527            }
 528
 0529            if (string.Equals(container, "mpg", StringComparison.OrdinalIgnoreCase))
 530            {
 0531                return null;
 532            }
 533
 0534            if (string.Equals(container, "mpeg", StringComparison.OrdinalIgnoreCase))
 535            {
 0536                return null;
 537            }
 538
 0539            if (string.Equals(container, "rec", StringComparison.OrdinalIgnoreCase))
 540            {
 0541                return null;
 542            }
 543
 0544            if (string.Equals(container, "dvr-ms", StringComparison.OrdinalIgnoreCase))
 545            {
 0546                return null;
 547            }
 548
 0549            if (string.Equals(container, "ogm", StringComparison.OrdinalIgnoreCase))
 550            {
 0551                return null;
 552            }
 553
 0554            if (string.Equals(container, "divx", StringComparison.OrdinalIgnoreCase))
 555            {
 0556                return null;
 557            }
 558
 0559            if (string.Equals(container, "tp", StringComparison.OrdinalIgnoreCase))
 560            {
 0561                return null;
 562            }
 563
 0564            if (string.Equals(container, "rmvb", StringComparison.OrdinalIgnoreCase))
 565            {
 0566                return null;
 567            }
 568
 0569            if (string.Equals(container, "rtp", StringComparison.OrdinalIgnoreCase))
 570            {
 0571                return null;
 572            }
 573
 574            // Seeing reported failures here, not sure yet if this is related to specifying input format
 0575            if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase))
 576            {
 0577                return null;
 578            }
 579
 580            // obviously don't do this for strm files
 0581            if (string.Equals(container, "strm", StringComparison.OrdinalIgnoreCase))
 582            {
 0583                return null;
 584            }
 585
 586            // ISO files don't have an ffmpeg format
 0587            if (string.Equals(container, "iso", StringComparison.OrdinalIgnoreCase))
 588            {
 0589                return null;
 590            }
 591
 0592            return container;
 593        }
 594
 595        /// <summary>
 596        /// Gets decoder from a codec.
 597        /// </summary>
 598        /// <param name="codec">Codec to use.</param>
 599        /// <returns>Decoder string.</returns>
 600        public string GetDecoderFromCodec(string codec)
 601        {
 602            // For these need to find out the ffmpeg names
 0603            if (string.Equals(codec, "mp2", StringComparison.OrdinalIgnoreCase))
 604            {
 0605                return null;
 606            }
 607
 0608            if (string.Equals(codec, "aac_latm", StringComparison.OrdinalIgnoreCase))
 609            {
 0610                return null;
 611            }
 612
 0613            if (string.Equals(codec, "eac3", StringComparison.OrdinalIgnoreCase))
 614            {
 0615                return null;
 616            }
 617
 0618            if (_mediaEncoder.SupportsDecoder(codec))
 619            {
 0620                return codec;
 621            }
 622
 0623            return null;
 624        }
 625
 626        /// <summary>
 627        /// Infers the audio codec based on the url.
 628        /// </summary>
 629        /// <param name="container">Container to use.</param>
 630        /// <returns>Codec string.</returns>
 631        public string InferAudioCodec(string container)
 632        {
 0633            if (string.IsNullOrWhiteSpace(container))
 634            {
 635                // this may not work, but if the client is that broken we cannot do anything better
 0636                return "aac";
 637            }
 638
 0639            var inferredCodec = container.ToLowerInvariant();
 640
 0641            return inferredCodec switch
 0642            {
 0643                "ogg" or "oga" or "ogv" or "webm" or "webma" => "opus",
 0644                "m4a" or "m4b" or "mp4" or "mov" or "mkv" or "mka" => "aac",
 0645                "ts" or "avi" or "flv" or "f4v" or "swf" => "mp3",
 0646                _ => inferredCodec
 0647            };
 648        }
 649
 650        /// <summary>
 651        /// Infers the video codec.
 652        /// </summary>
 653        /// <param name="url">The URL.</param>
 654        /// <returns>System.Nullable{VideoCodecs}.</returns>
 655        public string InferVideoCodec(string url)
 656        {
 0657            var ext = Path.GetExtension(url.AsSpan());
 658
 0659            if (ext.Equals(".asf", StringComparison.OrdinalIgnoreCase))
 660            {
 0661                return "wmv";
 662            }
 663
 0664            if (ext.Equals(".webm", StringComparison.OrdinalIgnoreCase))
 665            {
 666                // TODO: this may not always mean VP8, as the codec ages
 0667                return "vp8";
 668            }
 669
 0670            if (ext.Equals(".ogg", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ogv", StringComparison.OrdinalIgn
 671            {
 0672                return "theora";
 673            }
 674
 0675            if (ext.Equals(".m3u8", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ts", StringComparison.OrdinalIgn
 676            {
 0677                return "h264";
 678            }
 679
 0680            return "copy";
 681        }
 682
 683        public int GetVideoProfileScore(string videoCodec, string videoProfile)
 684        {
 685            // strip spaces because they may be stripped out on the query string
 0686            string profile = videoProfile.Replace(" ", string.Empty, StringComparison.Ordinal);
 0687            if (string.Equals("h264", videoCodec, StringComparison.OrdinalIgnoreCase))
 688            {
 0689                return Array.FindIndex(_videoProfilesH264, x => string.Equals(x, profile, StringComparison.OrdinalIgnore
 690            }
 691
 0692            if (string.Equals("hevc", videoCodec, StringComparison.OrdinalIgnoreCase))
 693            {
 0694                return Array.FindIndex(_videoProfilesH265, x => string.Equals(x, profile, StringComparison.OrdinalIgnore
 695            }
 696
 0697            if (string.Equals("av1", videoCodec, StringComparison.OrdinalIgnoreCase))
 698            {
 0699                return Array.FindIndex(_videoProfilesAv1, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreC
 700            }
 701
 0702            return -1;
 703        }
 704
 705        /// <summary>
 706        /// Gets the audio encoder.
 707        /// </summary>
 708        /// <param name="state">The state.</param>
 709        /// <returns>System.String.</returns>
 710        public string GetAudioEncoder(EncodingJobInfo state)
 711        {
 0712            var codec = state.OutputAudioCodec;
 713
 0714            if (!_validationRegex.IsMatch(codec))
 715            {
 0716                codec = "aac";
 717            }
 718
 0719            if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
 720            {
 721                // Use Apple's aac encoder if available as it provides best audio quality
 0722                if (_mediaEncoder.SupportsEncoder("aac_at"))
 723                {
 0724                    return "aac_at";
 725                }
 726
 727                // Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
 0728                if (_mediaEncoder.SupportsEncoder("libfdk_aac"))
 729                {
 0730                    return "libfdk_aac";
 731                }
 732
 0733                return "aac";
 734            }
 735
 0736            if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
 737            {
 0738                return "libmp3lame";
 739            }
 740
 0741            if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase))
 742            {
 0743                return "libvorbis";
 744            }
 745
 0746            if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
 747            {
 0748                return "libopus";
 749            }
 750
 0751            if (string.Equals(codec, "flac", StringComparison.OrdinalIgnoreCase))
 752            {
 0753                return "flac";
 754            }
 755
 0756            if (string.Equals(codec, "dts", StringComparison.OrdinalIgnoreCase))
 757            {
 0758                return "dca";
 759            }
 760
 0761            if (string.Equals(codec, "alac", StringComparison.OrdinalIgnoreCase))
 762            {
 763                // The ffmpeg upstream breaks the AudioToolbox ALAC encoder in version 6.1 but fixes it in version 7.0.
 764                // Since ALAC is lossless in quality and the AudioToolbox encoder is not faster,
 765                // its only benefit is a smaller file size.
 766                // To prevent problems, use the ffmpeg native encoder instead.
 0767                return "alac";
 768            }
 769
 0770            return codec.ToLowerInvariant();
 771        }
 772
 773        private string GetRkmppDeviceArgs(string alias)
 774        {
 0775            alias ??= RkmppAlias;
 776
 777            // device selection in rk is not supported.
 0778            return " -init_hw_device rkmpp=" + alias;
 779        }
 780
 781        private string GetVideoToolboxDeviceArgs(string alias)
 782        {
 0783            alias ??= VideotoolboxAlias;
 784
 785            // device selection in vt is not supported.
 0786            return " -init_hw_device videotoolbox=" + alias;
 787        }
 788
 789        private string GetCudaDeviceArgs(int deviceIndex, string alias)
 790        {
 0791            alias ??= CudaAlias;
 0792            deviceIndex = deviceIndex >= 0
 0793                ? deviceIndex
 0794                : 0;
 795
 0796            return string.Format(
 0797                CultureInfo.InvariantCulture,
 0798                " -init_hw_device cuda={0}:{1}",
 0799                alias,
 0800                deviceIndex);
 801        }
 802
 803        private string GetVulkanDeviceArgs(int deviceIndex, string deviceName, string srcDeviceAlias, string alias)
 804        {
 0805            alias ??= VulkanAlias;
 0806            deviceIndex = deviceIndex >= 0
 0807                ? deviceIndex
 0808                : 0;
 0809            var vendorOpts = string.IsNullOrEmpty(deviceName)
 0810                ? ":" + deviceIndex
 0811                : ":" + "\"" + deviceName + "\"";
 0812            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0813                ? vendorOpts
 0814                : "@" + srcDeviceAlias;
 815
 0816            return string.Format(
 0817                CultureInfo.InvariantCulture,
 0818                " -init_hw_device vulkan={0}{1}",
 0819                alias,
 0820                options);
 821        }
 822
 823        private string GetOpenclDeviceArgs(int deviceIndex, string deviceVendorName, string srcDeviceAlias, string alias
 824        {
 0825            alias ??= OpenclAlias;
 0826            deviceIndex = deviceIndex >= 0
 0827                ? deviceIndex
 0828                : 0;
 0829            var vendorOpts = string.IsNullOrEmpty(deviceVendorName)
 0830                ? ":0.0"
 0831                : ":." + deviceIndex + ",device_vendor=\"" + deviceVendorName + "\"";
 0832            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0833                ? vendorOpts
 0834                : "@" + srcDeviceAlias;
 835
 0836            return string.Format(
 0837                CultureInfo.InvariantCulture,
 0838                " -init_hw_device opencl={0}{1}",
 0839                alias,
 0840                options);
 841        }
 842
 843        private string GetD3d11vaDeviceArgs(int deviceIndex, string deviceVendorId, string alias)
 844        {
 0845            alias ??= D3d11vaAlias;
 0846            deviceIndex = deviceIndex >= 0 ? deviceIndex : 0;
 0847            var options = string.IsNullOrEmpty(deviceVendorId)
 0848                ? deviceIndex.ToString(CultureInfo.InvariantCulture)
 0849                : ",vendor=" + deviceVendorId;
 850
 0851            return string.Format(
 0852                CultureInfo.InvariantCulture,
 0853                " -init_hw_device d3d11va={0}:{1}",
 0854                alias,
 0855                options);
 856        }
 857
 858        private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string vendorId, st
 859        {
 0860            alias ??= VaapiAlias;
 0861            var haveVendorId = !string.IsNullOrEmpty(vendorId)
 0862                && _mediaEncoder.EncoderVersion >= _minFFmpegVaapiDeviceVendorId;
 863
 864            // Priority: 'renderNodePath' > 'vendorId' > 'kernelDriver'
 0865            var driverOpts = File.Exists(renderNodePath)
 0866                ? renderNodePath
 0867                : (haveVendorId ? $",vendor_id={vendorId}" : (string.IsNullOrEmpty(kernelDriver) ? string.Empty : $",ker
 868
 869            // 'driver' behaves similarly to env LIBVA_DRIVER_NAME
 0870            driverOpts += string.IsNullOrEmpty(driver) ? string.Empty : ",driver=" + driver;
 871
 0872            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0873                ? (string.IsNullOrEmpty(driverOpts) ? string.Empty : ":" + driverOpts)
 0874                : "@" + srcDeviceAlias;
 875
 0876            return string.Format(
 0877                CultureInfo.InvariantCulture,
 0878                " -init_hw_device vaapi={0}{1}",
 0879                alias,
 0880                options);
 881        }
 882
 883        private string GetDrmDeviceArgs(string renderNodePath, string alias)
 884        {
 0885            alias ??= DrmAlias;
 0886            renderNodePath = renderNodePath ?? "/dev/dri/renderD128";
 887
 0888            return string.Format(
 0889                CultureInfo.InvariantCulture,
 0890                " -init_hw_device drm={0}:{1}",
 0891                alias,
 0892                renderNodePath);
 893        }
 894
 895        private string GetQsvDeviceArgs(string renderNodePath, string alias)
 896        {
 0897            var arg = " -init_hw_device qsv=" + (alias ?? QsvAlias);
 0898            if (OperatingSystem.IsLinux())
 899            {
 900                // derive qsv from vaapi device
 0901                return GetVaapiDeviceArgs(renderNodePath, "iHD", "i915", "0x8086", null, VaapiAlias) + arg + "@" + Vaapi
 902            }
 903
 0904            if (OperatingSystem.IsWindows())
 905            {
 906                // on Windows, the deviceIndex is an int
 0907                if (int.TryParse(renderNodePath, NumberStyles.Integer, CultureInfo.InvariantCulture, out int deviceIndex
 908                {
 0909                    return GetD3d11vaDeviceArgs(deviceIndex, string.Empty, D3d11vaAlias) + arg + "@" + D3d11vaAlias;
 910                }
 911
 912                // derive qsv from d3d11va device
 0913                return GetD3d11vaDeviceArgs(0, "0x8086", D3d11vaAlias) + arg + "@" + D3d11vaAlias;
 914            }
 915
 0916            return null;
 917        }
 918
 919        private string GetFilterHwDeviceArgs(string alias)
 920        {
 0921            return string.IsNullOrEmpty(alias)
 0922                ? string.Empty
 0923                : " -filter_hw_device " + alias;
 924        }
 925
 926        public string GetGraphicalSubCanvasSize(EncodingJobInfo state)
 927        {
 928            // DVBSUB uses the fixed canvas size 720x576
 0929            if (state.SubtitleStream is not null
 0930                && ShouldEncodeSubtitle(state)
 0931                && !state.SubtitleStream.IsTextSubtitleStream
 0932                && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 933            {
 0934                var subtitleWidth = state.SubtitleStream?.Width;
 0935                var subtitleHeight = state.SubtitleStream?.Height;
 936
 0937                if (subtitleWidth.HasValue
 0938                    && subtitleHeight.HasValue
 0939                    && subtitleWidth.Value > 0
 0940                    && subtitleHeight.Value > 0)
 941                {
 0942                    return string.Format(
 0943                        CultureInfo.InvariantCulture,
 0944                        " -canvas_size {0}x{1}",
 0945                        subtitleWidth.Value,
 0946                        subtitleHeight.Value);
 947                }
 948            }
 949
 0950            return string.Empty;
 951        }
 952
 953        /// <summary>
 954        /// Gets the input video hwaccel argument.
 955        /// </summary>
 956        /// <param name="state">Encoding state.</param>
 957        /// <param name="options">Encoding options.</param>
 958        /// <returns>Input video hwaccel arguments.</returns>
 959        public string GetInputVideoHwaccelArgs(EncodingJobInfo state, EncodingOptions options)
 960        {
 0961            if (!state.IsVideoRequest)
 962            {
 0963                return string.Empty;
 964            }
 965
 0966            var vidEncoder = GetVideoEncoder(state, options) ?? string.Empty;
 0967            if (IsCopyCodec(vidEncoder))
 968            {
 0969                return string.Empty;
 970            }
 971
 0972            var args = new StringBuilder();
 0973            var isWindows = OperatingSystem.IsWindows();
 0974            var isLinux = OperatingSystem.IsLinux();
 0975            var isMacOS = OperatingSystem.IsMacOS();
 0976            var optHwaccelType = options.HardwareAccelerationType;
 0977            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 0978            var isHwTonemapAvailable = IsHwTonemapAvailable(state, options);
 979
 0980            if (optHwaccelType == HardwareAccelerationType.vaapi)
 981            {
 0982                if (!isLinux || !_mediaEncoder.SupportsHwaccel("vaapi"))
 983                {
 0984                    return string.Empty;
 985                }
 986
 0987                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 0988                var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 0989                if (!isVaapiDecoder && !isVaapiEncoder)
 990                {
 0991                    return string.Empty;
 992                }
 993
 0994                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 995                {
 0996                    args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "iHD", null, null, null, VaapiAlias));
 997                }
 0998                else if (_mediaEncoder.IsVaapiDeviceInteli965)
 999                {
 1000                    // Only override i965 since it has lower priority than iHD in libva lookup.
 01001                    Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME", "i965");
 01002                    Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME_JELLYFIN", "i965");
 01003                    args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "i965", null, null, null, VaapiAlias));
 1004                }
 1005
 01006                var filterDevArgs = string.Empty;
 01007                var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
 1008
 01009                if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 1010                {
 01011                    if (doOclTonemap && !isVaapiDecoder)
 1012                    {
 01013                        args.Append(GetOpenclDeviceArgs(0, null, VaapiAlias, OpenclAlias));
 01014                        filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1015                    }
 1016                }
 01017                else if (_mediaEncoder.IsVaapiDeviceAmd)
 1018                {
 1019                    // Disable AMD EFC feature since it's still unstable in upstream Mesa.
 01020                    Environment.SetEnvironmentVariable("AMD_DEBUG", "noefc");
 1021
 01022                    if (IsVulkanFullSupported()
 01023                        && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 01024                        && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 1025                    {
 01026                        args.Append(GetDrmDeviceArgs(options.VaapiDevice, DrmAlias));
 01027                        args.Append(GetVaapiDeviceArgs(null, null, null, null, DrmAlias, VaapiAlias));
 01028                        args.Append(GetVulkanDeviceArgs(0, null, DrmAlias, VulkanAlias));
 1029
 1030                        // libplacebo wants an explicitly set vulkan filter device.
 01031                        filterDevArgs = GetFilterHwDeviceArgs(VulkanAlias);
 1032                    }
 1033                    else
 1034                    {
 01035                        args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, null, null, VaapiAlias));
 01036                        filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias);
 1037
 01038                        if (doOclTonemap)
 1039                        {
 1040                            // ROCm/ROCr OpenCL runtime
 01041                            args.Append(GetOpenclDeviceArgs(0, "Advanced Micro Devices", null, OpenclAlias));
 01042                            filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1043                        }
 1044                    }
 1045                }
 01046                else if (doOclTonemap)
 1047                {
 01048                    args.Append(GetOpenclDeviceArgs(0, null, null, OpenclAlias));
 01049                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1050                }
 1051
 01052                args.Append(filterDevArgs);
 1053            }
 01054            else if (optHwaccelType == HardwareAccelerationType.qsv)
 1055            {
 01056                if ((!isLinux && !isWindows) || !_mediaEncoder.SupportsHwaccel("qsv"))
 1057                {
 01058                    return string.Empty;
 1059                }
 1060
 01061                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 01062                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01063                var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 01064                var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 01065                var isHwDecoder = isQsvDecoder || isVaapiDecoder || isD3d11vaDecoder;
 01066                if (!isHwDecoder && !isQsvEncoder)
 1067                {
 01068                    return string.Empty;
 1069                }
 1070
 01071                args.Append(GetQsvDeviceArgs(options.QsvDevice, QsvAlias));
 01072                var filterDevArgs = GetFilterHwDeviceArgs(QsvAlias);
 1073                // child device used by qsv.
 01074                if (_mediaEncoder.SupportsHwaccel("vaapi") || _mediaEncoder.SupportsHwaccel("d3d11va"))
 1075                {
 01076                    if (isHwTonemapAvailable && IsOpenclFullSupported())
 1077                    {
 01078                        var srcAlias = isLinux ? VaapiAlias : D3d11vaAlias;
 01079                        args.Append(GetOpenclDeviceArgs(0, null, srcAlias, OpenclAlias));
 01080                        if (!isHwDecoder)
 1081                        {
 01082                            filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1083                        }
 1084                    }
 1085                }
 1086
 01087                args.Append(filterDevArgs);
 1088            }
 01089            else if (optHwaccelType == HardwareAccelerationType.nvenc)
 1090            {
 01091                if ((!isLinux && !isWindows) || !IsCudaFullSupported())
 1092                {
 01093                    return string.Empty;
 1094                }
 1095
 01096                var isCuvidDecoder = vidDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase);
 01097                var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 01098                var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 01099                var isHwDecoder = isNvdecDecoder || isCuvidDecoder;
 01100                if (!isHwDecoder && !isNvencEncoder)
 1101                {
 01102                    return string.Empty;
 1103                }
 1104
 01105                args.Append(GetCudaDeviceArgs(0, CudaAlias))
 01106                     .Append(GetFilterHwDeviceArgs(CudaAlias));
 1107            }
 01108            else if (optHwaccelType == HardwareAccelerationType.amf)
 1109            {
 01110                if (!isWindows || !_mediaEncoder.SupportsHwaccel("d3d11va"))
 1111                {
 01112                    return string.Empty;
 1113                }
 1114
 01115                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 01116                var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 01117                if (!isD3d11vaDecoder && !isAmfEncoder)
 1118                {
 01119                    return string.Empty;
 1120                }
 1121
 1122                // no dxva video processor hw filter.
 01123                args.Append(GetD3d11vaDeviceArgs(0, "0x1002", D3d11vaAlias));
 01124                var filterDevArgs = string.Empty;
 01125                if (IsOpenclFullSupported())
 1126                {
 01127                    args.Append(GetOpenclDeviceArgs(0, null, D3d11vaAlias, OpenclAlias));
 01128                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1129                }
 1130
 01131                args.Append(filterDevArgs);
 1132            }
 01133            else if (optHwaccelType == HardwareAccelerationType.videotoolbox)
 1134            {
 01135                if (!isMacOS || !_mediaEncoder.SupportsHwaccel("videotoolbox"))
 1136                {
 01137                    return string.Empty;
 1138                }
 1139
 01140                var isVideotoolboxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 01141                var isVideotoolboxEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 01142                if (!isVideotoolboxDecoder && !isVideotoolboxEncoder)
 1143                {
 01144                    return string.Empty;
 1145                }
 1146
 1147                // videotoolbox hw filter does not require device selection
 01148                args.Append(GetVideoToolboxDeviceArgs(VideotoolboxAlias));
 1149            }
 01150            else if (optHwaccelType == HardwareAccelerationType.rkmpp)
 1151            {
 01152                if (!isLinux || !_mediaEncoder.SupportsHwaccel("rkmpp"))
 1153                {
 01154                    return string.Empty;
 1155                }
 1156
 01157                var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 01158                var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 01159                if (!isRkmppDecoder && !isRkmppEncoder)
 1160                {
 01161                    return string.Empty;
 1162                }
 1163
 01164                args.Append(GetRkmppDeviceArgs(RkmppAlias));
 1165
 01166                var filterDevArgs = string.Empty;
 01167                var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
 1168
 01169                if (doOclTonemap && !isRkmppDecoder)
 1170                {
 01171                    args.Append(GetOpenclDeviceArgs(0, null, RkmppAlias, OpenclAlias));
 01172                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1173                }
 1174
 01175                args.Append(filterDevArgs);
 1176            }
 1177
 01178            if (!string.IsNullOrEmpty(vidDecoder))
 1179            {
 01180                args.Append(vidDecoder);
 1181            }
 1182
 01183            return args.ToString().Trim();
 1184        }
 1185
 1186        /// <summary>
 1187        /// Gets the input argument.
 1188        /// </summary>
 1189        /// <param name="state">Encoding state.</param>
 1190        /// <param name="options">Encoding options.</param>
 1191        /// <param name="segmentContainer">Segment Container.</param>
 1192        /// <returns>Input arguments.</returns>
 1193        public string GetInputArgument(EncodingJobInfo state, EncodingOptions options, string segmentContainer)
 1194        {
 01195            var arg = new StringBuilder();
 01196            var inputVidHwaccelArgs = GetInputVideoHwaccelArgs(state, options);
 1197
 01198            if (!string.IsNullOrEmpty(inputVidHwaccelArgs))
 1199            {
 01200                arg.Append(inputVidHwaccelArgs);
 1201            }
 1202
 01203            var canvasArgs = GetGraphicalSubCanvasSize(state);
 01204            if (!string.IsNullOrEmpty(canvasArgs))
 1205            {
 01206                arg.Append(canvasArgs);
 1207            }
 1208
 01209            if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
 1210            {
 01211                var concatFilePath = Path.Join(_configurationManager.CommonApplicationPaths.CachePath, "concat", state.M
 01212                if (!File.Exists(concatFilePath))
 1213                {
 01214                    _mediaEncoder.GenerateConcatConfig(state.MediaSource, concatFilePath);
 1215                }
 1216
 01217                arg.Append(" -f concat -safe 0 -i \"")
 01218                    .Append(concatFilePath)
 01219                    .Append("\" ");
 1220            }
 1221            else
 1222            {
 01223                arg.Append(" -i ")
 01224                    .Append(_mediaEncoder.GetInputPathArgument(state));
 1225            }
 1226
 1227            // sub2video for external graphical subtitles
 01228            if (state.SubtitleStream is not null
 01229                && ShouldEncodeSubtitle(state)
 01230                && !state.SubtitleStream.IsTextSubtitleStream
 01231                && state.SubtitleStream.IsExternal)
 1232            {
 01233                var subtitlePath = state.SubtitleStream.Path;
 01234                var subtitleExtension = Path.GetExtension(subtitlePath.AsSpan());
 1235
 1236                // dvdsub/vobsub graphical subtitles use .sub+.idx pairs
 01237                if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase))
 1238                {
 01239                    var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
 01240                    if (File.Exists(idxFile))
 1241                    {
 01242                        subtitlePath = idxFile;
 1243                    }
 1244                }
 1245
 1246                // Also seek the external subtitles stream.
 01247                var seekSubParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01248                if (!string.IsNullOrEmpty(seekSubParam))
 1249                {
 01250                    arg.Append(' ').Append(seekSubParam);
 1251                }
 1252
 01253                if (!string.IsNullOrEmpty(canvasArgs))
 1254                {
 01255                    arg.Append(canvasArgs);
 1256                }
 1257
 01258                arg.Append(" -i file:\"").Append(subtitlePath).Append('\"');
 1259            }
 1260
 01261            if (state.AudioStream is not null && state.AudioStream.IsExternal)
 1262            {
 1263                // Also seek the external audio stream.
 01264                var seekAudioParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01265                if (!string.IsNullOrEmpty(seekAudioParam))
 1266                {
 01267                    arg.Append(' ').Append(seekAudioParam);
 1268                }
 1269
 01270                arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
 1271            }
 1272
 1273            // Disable auto inserted SW scaler for HW decoders in case of changed resolution.
 01274            var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
 01275            if (!isSwDecoder)
 1276            {
 01277                arg.Append(" -noautoscale");
 1278            }
 1279
 01280            return arg.ToString();
 1281        }
 1282
 1283        /// <summary>
 1284        /// Determines whether the specified stream is H264.
 1285        /// </summary>
 1286        /// <param name="stream">The stream.</param>
 1287        /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
 1288        public static bool IsH264(MediaStream stream)
 1289        {
 01290            var codec = stream.Codec ?? string.Empty;
 1291
 01292            return codec.Contains("264", StringComparison.OrdinalIgnoreCase)
 01293                    || codec.Contains("avc", StringComparison.OrdinalIgnoreCase);
 1294        }
 1295
 1296        public static bool IsH265(MediaStream stream)
 1297        {
 01298            var codec = stream.Codec ?? string.Empty;
 1299
 01300            return codec.Contains("265", StringComparison.OrdinalIgnoreCase)
 01301                || codec.Contains("hevc", StringComparison.OrdinalIgnoreCase);
 1302        }
 1303
 1304        public static bool IsAAC(MediaStream stream)
 1305        {
 01306            var codec = stream.Codec ?? string.Empty;
 1307
 01308            return codec.Contains("aac", StringComparison.OrdinalIgnoreCase);
 1309        }
 1310
 1311        public static string GetBitStreamArgs(MediaStream stream)
 1312        {
 1313            // TODO This is auto inserted into the mpegts mux so it might not be needed.
 1314            // https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
 01315            if (IsH264(stream))
 1316            {
 01317                return "-bsf:v h264_mp4toannexb";
 1318            }
 1319
 01320            if (IsH265(stream))
 1321            {
 01322                return "-bsf:v hevc_mp4toannexb";
 1323            }
 1324
 01325            if (IsAAC(stream))
 1326            {
 1327                // Convert adts header(mpegts) to asc header(mp4).
 01328                return "-bsf:a aac_adtstoasc";
 1329            }
 1330
 01331            return null;
 1332        }
 1333
 1334        public static string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSour
 1335        {
 01336            var bitStreamArgs = string.Empty;
 01337            var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
 1338
 1339            // Apply aac_adtstoasc bitstream filter when media source is in mpegts.
 01340            if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)
 01341                && (string.Equals(mediaSourceContainer, "ts", StringComparison.OrdinalIgnoreCase)
 01342                    || string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase)
 01343                    || string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase)))
 1344            {
 01345                bitStreamArgs = GetBitStreamArgs(state.AudioStream);
 01346                bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs;
 1347            }
 1348
 01349            return bitStreamArgs;
 1350        }
 1351
 1352        public static string GetSegmentFileExtension(string segmentContainer)
 1353        {
 01354            if (!string.IsNullOrWhiteSpace(segmentContainer))
 1355            {
 01356                return "." + segmentContainer;
 1357            }
 1358
 01359            return ".ts";
 1360        }
 1361
 1362        private string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
 1363        {
 01364            if (state.OutputVideoBitrate is null)
 1365            {
 01366                return string.Empty;
 1367            }
 1368
 01369            int bitrate = state.OutputVideoBitrate.Value;
 1370
 1371            // Bit rate under 1000k is not allowed in h264_qsv
 01372            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 1373            {
 01374                bitrate = Math.Max(bitrate, 1000);
 1375            }
 1376
 1377            // Currently use the same buffer size for all encoders
 01378            int bufsize = bitrate * 2;
 1379
 01380            if (string.Equals(videoCodec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1381            {
 01382                return FormattableString.Invariant($" -b:v {bitrate} -bufsize {bufsize}");
 1383            }
 1384
 01385            if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)
 01386                || string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
 1387            {
 01388                return FormattableString.Invariant($" -maxrate {bitrate} -bufsize {bufsize}");
 1389            }
 1390
 01391            if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01392                || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01393                || string.Equals(videoCodec, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1394            {
 1395                // Override the too high default qmin 18 in transcoding preset
 01396                return FormattableString.Invariant($" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsiz
 1397            }
 1398
 01399            if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01400                || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01401                || string.Equals(videoCodec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1402            {
 1403                // VBR in i965 driver may result in pixelated output.
 01404                if (_mediaEncoder.IsVaapiDeviceInteli965)
 1405                {
 01406                    return FormattableString.Invariant($" -rc_mode CBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsi
 1407                }
 1408
 01409                return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"
 1410            }
 1411
 01412            if (string.Equals(videoCodec, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 01413                || string.Equals(videoCodec, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase))
 1414            {
 1415                // The `maxrate` and `bufsize` options can potentially lead to performance regression
 1416                // and even encoder hangs, especially when the value is very high.
 01417                return FormattableString.Invariant($" -b:v {bitrate} -qmin -1 -qmax -1");
 1418            }
 1419
 01420            return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
 1421        }
 1422
 1423        private string GetEncoderParam(EncoderPreset? preset, EncoderPreset defaultPreset, EncodingOptions encodingOptio
 1424        {
 01425            var param = string.Empty;
 01426            var encoderPreset = preset ?? defaultPreset;
 01427            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
 1428            {
 01429                var presetString = encoderPreset switch
 01430                {
 01431                    EncoderPreset.auto => EncoderPreset.veryfast.ToString().ToLowerInvariant(),
 01432                    _ => encoderPreset.ToString().ToLowerInvariant()
 01433                };
 1434
 01435                param += " -preset " + presetString;
 1436
 01437                int encodeCrf = encodingOptions.H264Crf;
 01438                if (isLibX265)
 1439                {
 01440                    encodeCrf = encodingOptions.H265Crf;
 1441                }
 1442
 01443                if (encodeCrf >= 0 && encodeCrf <= 51)
 1444                {
 01445                    param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture);
 1446                }
 1447                else
 1448                {
 01449                    string defaultCrf = "23";
 01450                    if (isLibX265)
 1451                    {
 01452                        defaultCrf = "28";
 1453                    }
 1454
 01455                    param += " -crf " + defaultCrf;
 1456                }
 1457            }
 01458            else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1459            {
 1460                // Default to use the recommended preset 10.
 1461                // Omit presets < 5, which are too slow for on the fly encoding.
 1462                // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md
 01463                param += encoderPreset switch
 01464                {
 01465                    EncoderPreset.veryslow => " -preset 5",
 01466                    EncoderPreset.slower => " -preset 6",
 01467                    EncoderPreset.slow => " -preset 7",
 01468                    EncoderPreset.medium => " -preset 8",
 01469                    EncoderPreset.fast => " -preset 9",
 01470                    EncoderPreset.faster => " -preset 10",
 01471                    EncoderPreset.veryfast => " -preset 11",
 01472                    EncoderPreset.superfast => " -preset 12",
 01473                    EncoderPreset.ultrafast => " -preset 13",
 01474                    _ => " -preset 10"
 01475                };
 1476            }
 01477            else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01478                     || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01479                     || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1480            {
 1481                // -compression_level is not reliable on AMD.
 01482                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 1483                {
 01484                    param += encoderPreset switch
 01485                    {
 01486                        EncoderPreset.veryslow => " -compression_level 1",
 01487                        EncoderPreset.slower => " -compression_level 2",
 01488                        EncoderPreset.slow => " -compression_level 3",
 01489                        EncoderPreset.medium => " -compression_level 4",
 01490                        EncoderPreset.fast => " -compression_level 5",
 01491                        EncoderPreset.faster => " -compression_level 6",
 01492                        EncoderPreset.veryfast => " -compression_level 7",
 01493                        EncoderPreset.superfast => " -compression_level 7",
 01494                        EncoderPreset.ultrafast => " -compression_level 7",
 01495                        _ => string.Empty
 01496                    };
 1497                }
 1498            }
 01499            else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv)
 01500                     || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv)
 01501                     || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv)
 1502            {
 01503                EncoderPreset[] valid_presets = [EncoderPreset.veryslow, EncoderPreset.slower, EncoderPreset.slow, Encod
 1504
 01505                param += " -preset " + (valid_presets.Contains(encoderPreset) ? encoderPreset : EncoderPreset.veryfast).
 1506            }
 01507            else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
 01508                        || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_n
 01509                        || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase) // av1 (av1_nven
 01510            )
 1511            {
 01512                param += encoderPreset switch
 01513                {
 01514                        EncoderPreset.veryslow => " -preset p7",
 01515                        EncoderPreset.slower => " -preset p6",
 01516                        EncoderPreset.slow => " -preset p5",
 01517                        EncoderPreset.medium => " -preset p4",
 01518                        EncoderPreset.fast => " -preset p3",
 01519                        EncoderPreset.faster => " -preset p2",
 01520                        _ => " -preset p1"
 01521                };
 1522            }
 01523            else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf)
 01524                        || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf
 01525                        || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase) // av1 (av1_amf)
 01526            )
 1527            {
 01528                param += encoderPreset switch
 01529                {
 01530                        EncoderPreset.veryslow => " -quality quality",
 01531                        EncoderPreset.slower => " -quality quality",
 01532                        EncoderPreset.slow => " -quality quality",
 01533                        EncoderPreset.medium => " -quality balanced",
 01534                        _ => " -quality speed"
 01535                };
 1536
 01537                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01538                    || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1539                {
 01540                    param += " -header_insertion_mode gop";
 1541                }
 1542
 01543                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
 1544                {
 01545                    param += " -gops_per_idr 1";
 1546                }
 1547            }
 01548            else if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase) // h264 (h264_
 01549                        || string.Equals(videoEncoder, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase) // hevc 
 01550            )
 1551            {
 01552                param += encoderPreset switch
 01553                {
 01554                        EncoderPreset.veryslow => " -prio_speed 0",
 01555                        EncoderPreset.slower => " -prio_speed 0",
 01556                        EncoderPreset.slow => " -prio_speed 0",
 01557                        EncoderPreset.medium => " -prio_speed 0",
 01558                        _ => " -prio_speed 1"
 01559                };
 1560            }
 1561
 01562            return param;
 1563        }
 1564
 1565        public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
 1566        {
 01567            if (double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel))
 1568            {
 01569                if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase))
 1570                {
 1571                    // Transcode to level 5.3 (15) and lower for maximum compatibility.
 1572                    // https://en.wikipedia.org/wiki/AV1#Levels
 01573                    if (requestLevel < 0 || requestLevel >= 15)
 1574                    {
 01575                        return "15";
 1576                    }
 1577                }
 01578                else if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 01579                         || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
 1580                {
 1581                    // Transcode to level 5.0 and lower for maximum compatibility.
 1582                    // Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it.
 1583                    // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
 1584                    // MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880.
 01585                    if (requestLevel < 0 || requestLevel >= 150)
 1586                    {
 01587                        return "150";
 1588                    }
 1589                }
 01590                else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
 1591                {
 1592                    // Transcode to level 5.1 and lower for maximum compatibility.
 1593                    // h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
 1594                    // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
 01595                    if (requestLevel < 0 || requestLevel >= 51)
 1596                    {
 01597                        return "51";
 1598                    }
 1599                }
 1600            }
 1601
 01602            return level;
 1603        }
 1604
 1605        /// <summary>
 1606        /// Gets the text subtitle param.
 1607        /// </summary>
 1608        /// <param name="state">The state.</param>
 1609        /// <param name="enableAlpha">Enable alpha processing.</param>
 1610        /// <param name="enableSub2video">Enable sub2video mode.</param>
 1611        /// <returns>System.String.</returns>
 1612        public string GetTextSubtitlesFilter(EncodingJobInfo state, bool enableAlpha, bool enableSub2video)
 1613        {
 01614            var seconds = Math.Round(TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds);
 1615
 1616            // hls always copies timestamps
 01617            var setPtsParam = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive
 01618                ? string.Empty
 01619                : string.Format(CultureInfo.InvariantCulture, ",setpts=PTS -{0}/TB", seconds);
 1620
 01621            var alphaParam = enableAlpha ? ":alpha=1" : string.Empty;
 01622            var sub2videoParam = enableSub2video ? ":sub2video=1" : string.Empty;
 1623
 01624            var fontPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id);
 01625            var fontParam = string.Format(
 01626                CultureInfo.InvariantCulture,
 01627                ":fontsdir='{0}'",
 01628                _mediaEncoder.EscapeSubtitleFilterPath(fontPath));
 1629
 01630            if (state.SubtitleStream.IsExternal)
 1631            {
 01632                var charsetParam = string.Empty;
 1633
 01634                if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
 1635                {
 01636                    var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
 01637                            state.SubtitleStream,
 01638                            state.SubtitleStream.Language,
 01639                            state.MediaSource,
 01640                            CancellationToken.None).GetAwaiter().GetResult();
 1641
 01642                    if (!string.IsNullOrEmpty(charenc))
 1643                    {
 01644                        charsetParam = ":charenc=" + charenc;
 1645                    }
 1646                }
 1647
 01648                return string.Format(
 01649                    CultureInfo.InvariantCulture,
 01650                    "subtitles=f='{0}'{1}{2}{3}{4}{5}",
 01651                    _mediaEncoder.EscapeSubtitleFilterPath(state.SubtitleStream.Path),
 01652                    charsetParam,
 01653                    alphaParam,
 01654                    sub2videoParam,
 01655                    fontParam,
 01656                    setPtsParam);
 1657            }
 1658
 01659            var subtitlePath = _subtitleEncoder.GetSubtitleFilePath(
 01660                    state.SubtitleStream,
 01661                    state.MediaSource,
 01662                    CancellationToken.None).GetAwaiter().GetResult();
 1663
 01664            return string.Format(
 01665                CultureInfo.InvariantCulture,
 01666                "subtitles=f='{0}'{1}{2}{3}{4}",
 01667                _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
 01668                alphaParam,
 01669                sub2videoParam,
 01670                fontParam,
 01671                setPtsParam);
 1672        }
 1673
 1674        public double? GetFramerateParam(EncodingJobInfo state)
 1675        {
 01676            var request = state.BaseRequest;
 1677
 01678            if (request.Framerate.HasValue)
 1679            {
 01680                return request.Framerate.Value;
 1681            }
 1682
 01683            var maxrate = request.MaxFramerate;
 1684
 01685            if (maxrate.HasValue && state.VideoStream is not null)
 1686            {
 01687                var contentRate = state.VideoStream.ReferenceFrameRate;
 1688
 01689                if (contentRate.HasValue && contentRate.Value > maxrate.Value)
 1690                {
 01691                    return maxrate;
 1692                }
 1693            }
 1694
 01695            return null;
 1696        }
 1697
 1698        public string GetHlsVideoKeyFrameArguments(
 1699            EncodingJobInfo state,
 1700            string codec,
 1701            int segmentLength,
 1702            bool isEventPlaylist,
 1703            int? startNumber)
 1704        {
 01705            var args = string.Empty;
 01706            var gopArg = string.Empty;
 1707
 01708            var keyFrameArg = string.Format(
 01709                CultureInfo.InvariantCulture,
 01710                " -force_key_frames:0 \"expr:gte(t,n_forced*{0})\"",
 01711                segmentLength);
 1712
 01713            var framerate = state.VideoStream?.RealFrameRate;
 01714            if (framerate.HasValue)
 1715            {
 1716                // This is to make sure keyframe interval is limited to our segment,
 1717                // as forcing keyframes is not enough.
 1718                // Example: we encoded half of desired length, then codec detected
 1719                // scene cut and inserted a keyframe; next forced keyframe would
 1720                // be created outside of segment, which breaks seeking.
 01721                gopArg = string.Format(
 01722                    CultureInfo.InvariantCulture,
 01723                    " -g:v:0 {0} -keyint_min:v:0 {0}",
 01724                    Math.Ceiling(segmentLength * framerate.Value));
 1725            }
 1726
 1727            // Unable to force key frames using these encoders, set key frames by GOP.
 01728            if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01729                || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 01730                || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01731                || string.Equals(codec, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 01732                || string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 01733                || string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 01734                || string.Equals(codec, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase)
 01735                || string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 01736                || string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 01737                || string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase)
 01738                || string.Equals(codec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1739            {
 01740                args += gopArg;
 1741            }
 01742            else if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase)
 01743                     || string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)
 01744                     || string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01745                     || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01746                     || string.Equals(codec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1747            {
 01748                args += keyFrameArg;
 1749
 1750                // prevent the libx264 from post processing to break the set keyframe.
 01751                if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase))
 1752                {
 01753                    args += " -sc_threshold:v:0 0";
 1754                }
 1755            }
 1756            else
 1757            {
 01758                args += keyFrameArg + gopArg;
 1759            }
 1760
 1761            // global_header produced by AMD HEVC VA-API encoder causes non-playable fMP4 on iOS
 01762            if (string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01763                && _mediaEncoder.IsVaapiDeviceAmd)
 1764            {
 01765                args += " -flags:v -global_header";
 1766            }
 1767
 01768            return args;
 1769        }
 1770
 1771        /// <summary>
 1772        /// Gets the video bitrate to specify on the command line.
 1773        /// </summary>
 1774        /// <param name="state">Encoding state.</param>
 1775        /// <param name="videoEncoder">Video encoder to use.</param>
 1776        /// <param name="encodingOptions">Encoding options.</param>
 1777        /// <param name="defaultPreset">Default present to use for encoding.</param>
 1778        /// <returns>Video bitrate.</returns>
 1779        public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, 
 1780        {
 01781            var param = string.Empty;
 1782
 1783            // Tutorials: Enable Intel GuC / HuC firmware loading for Low Power Encoding.
 1784            // https://01.org/group/43/downloads/firmware
 1785            // https://wiki.archlinux.org/title/intel_graphics#Enable_GuC_/_HuC_firmware_loading
 1786            // Intel Low Power Encoding can save unnecessary CPU-GPU synchronization,
 1787            // which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
 01788            var intelLowPowerHwEncoding = false;
 1789
 1790            // Workaround for linux 5.18 to 6.1.3 i915 hang at cost of performance.
 1791            // https://github.com/intel/media-driver/issues/1456
 01792            var enableWaFori915Hang = false;
 1793
 01794            var hardwareAccelerationType = encodingOptions.HardwareAccelerationType;
 1795
 01796            if (hardwareAccelerationType == HardwareAccelerationType.vaapi)
 1797            {
 01798                var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
 1799
 01800                if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
 1801                {
 01802                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder && isIntelVaapiDriver;
 1803                }
 01804                else if (string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
 1805                {
 01806                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder && isIntelVaapiDriver;
 1807                }
 1808            }
 01809            else if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 1810            {
 01811                if (OperatingSystem.IsLinux())
 1812                {
 01813                    var ver = Environment.OSVersion.Version;
 01814                    var isFixedKernel60 = ver.Major == 6 && ver.Minor == 0 && ver >= _minFixedKernel60i915Hang;
 01815                    var isUnaffectedKernel = ver < _minKerneli915Hang || ver > _maxKerneli915Hang;
 1816
 01817                    if (!(isUnaffectedKernel || isFixedKernel60))
 1818                    {
 01819                        var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
 01820                        var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
 01821                                             || vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01822                        var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
 01823                            && IsVaapiSupported(state)
 01824                            && IsOpenclFullSupported()
 01825                            && !IsIntelVppTonemapAvailable(state, encodingOptions)
 01826                            && IsHwTonemapAvailable(state, encodingOptions);
 1827
 01828                        enableWaFori915Hang = isIntelDecoder && doOclTonemap;
 1829                    }
 1830                }
 1831
 01832                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 1833                {
 01834                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder;
 1835                }
 01836                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 1837                {
 01838                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder;
 1839                }
 1840                else
 1841                {
 01842                    enableWaFori915Hang = false;
 1843                }
 1844            }
 1845
 01846            if (intelLowPowerHwEncoding)
 1847            {
 01848                param += " -low_power 1";
 1849            }
 1850
 01851            if (enableWaFori915Hang)
 1852            {
 01853                param += " -async_depth 1";
 1854            }
 1855
 01856            var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
 01857            var encodingPreset = encodingOptions.EncoderPreset;
 1858
 01859            param += GetEncoderParam(encodingPreset, defaultPreset, encodingOptions, videoEncoder, isLibX265);
 01860            param += GetVideoBitrateParam(state, videoEncoder);
 1861
 01862            var framerate = GetFramerateParam(state);
 01863            if (framerate.HasValue)
 1864            {
 01865                param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(CultureInfo.Inv
 1866            }
 1867
 01868            var targetVideoCodec = state.ActualOutputVideoCodec;
 01869            if (string.Equals(targetVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
 01870                || string.Equals(targetVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
 1871            {
 01872                targetVideoCodec = "hevc";
 1873            }
 1874
 01875            var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
 01876            profile = WhiteSpaceRegex().Replace(profile, string.Empty).ToLowerInvariant();
 1877
 01878            var videoProfiles = Array.Empty<string>();
 01879            if (string.Equals("h264", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 1880            {
 01881                videoProfiles = _videoProfilesH264;
 1882            }
 01883            else if (string.Equals("hevc", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 1884            {
 01885                videoProfiles = _videoProfilesH265;
 1886            }
 01887            else if (string.Equals("av1", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 1888            {
 01889                videoProfiles = _videoProfilesAv1;
 1890            }
 1891
 01892            if (!videoProfiles.Contains(profile, StringComparison.OrdinalIgnoreCase))
 1893            {
 01894                profile = string.Empty;
 1895            }
 1896
 1897            // We only transcode to HEVC 8-bit for now, force Main Profile.
 01898            if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)
 01899                || profile.Contains("mainstill", StringComparison.OrdinalIgnoreCase))
 1900            {
 01901                profile = "main";
 1902            }
 1903
 1904            // Extended Profile is not supported by any known h264 encoders, force Main Profile.
 01905            if (profile.Contains("extended", StringComparison.OrdinalIgnoreCase))
 1906            {
 01907                profile = "main";
 1908            }
 1909
 1910            // Only libx264 support encoding H264 High 10 Profile, otherwise force High Profile.
 01911            if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 01912                && profile.Contains("high10", StringComparison.OrdinalIgnoreCase))
 1913            {
 01914                profile = "high";
 1915            }
 1916
 1917            // We only need Main profile of AV1 encoders.
 01918            if (videoEncoder.Contains("av1", StringComparison.OrdinalIgnoreCase)
 01919                && (profile.Contains("high", StringComparison.OrdinalIgnoreCase)
 01920                    || profile.Contains("professional", StringComparison.OrdinalIgnoreCase)))
 1921            {
 01922                profile = "main";
 1923            }
 1924
 1925            // h264_vaapi does not support Baseline profile, force Constrained Baseline in this case,
 1926            // which is compatible (and ugly).
 01927            if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01928                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 1929            {
 01930                profile = "constrained_baseline";
 1931            }
 1932
 1933            // libx264, h264_{qsv,nvenc,rkmpp} does not support Constrained Baseline profile, force Baseline in this cas
 01934            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 01935                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01936                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 01937                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 01938                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 1939            {
 01940                profile = "baseline";
 1941            }
 1942
 1943            // libx264, h264_{qsv,nvenc,vaapi,rkmpp} does not support Constrained High profile, force High in this case.
 01944            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 01945                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01946                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 01947                 || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01948                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 01949                && profile.Contains("high", StringComparison.OrdinalIgnoreCase))
 1950            {
 01951                profile = "high";
 1952            }
 1953
 01954            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01955                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 1956            {
 01957                profile = "constrained_baseline";
 1958            }
 1959
 01960            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01961                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 1962            {
 01963                profile = "constrained_high";
 1964            }
 1965
 01966            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 01967                && profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase))
 1968            {
 01969                profile = "constrained_baseline";
 1970            }
 1971
 01972            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 01973                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 1974            {
 01975                profile = "constrained_high";
 1976            }
 1977
 01978            if (!string.IsNullOrEmpty(profile))
 1979            {
 1980                // Currently there's no profile option in av1_nvenc encoder
 01981                if (!(string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 01982                      || string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)))
 1983                {
 01984                    param += " -profile:v:0 " + profile;
 1985                }
 1986            }
 1987
 01988            var level = state.GetRequestedLevel(targetVideoCodec);
 1989
 01990            if (!string.IsNullOrEmpty(level))
 1991            {
 01992                level = NormalizeTranscodingLevel(state, level);
 1993
 1994                // libx264, QSV, AMF can adjust the given level to match the output.
 01995                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01996                    || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 1997                {
 01998                    param += " -level " + level;
 1999                }
 02000                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2001                {
 2002                    // hevc_qsv use -level 51 instead of -level 153.
 02003                    if (double.TryParse(level, CultureInfo.InvariantCulture, out double hevcLevel))
 2004                    {
 02005                        param += " -level " + (hevcLevel / 3);
 2006                    }
 2007                }
 02008                else if (string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 02009                         || string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 2010                {
 2011                    // libsvtav1 and av1_qsv use -level 60 instead of -level 16
 2012                    // https://aomedia.org/av1/specification/annex-a/
 02013                    if (int.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out int av1Level))
 2014                    {
 02015                        var x = 2 + (av1Level >> 2);
 02016                        var y = av1Level & 3;
 02017                        var res = (x * 10) + y;
 02018                        param += " -level " + res;
 2019                    }
 2020                }
 02021                else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02022                         || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 02023                         || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 2024                {
 02025                    param += " -level " + level;
 2026                }
 02027                else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02028                         || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 02029                         || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase))
 2030                {
 2031                    // level option may cause NVENC to fail.
 2032                    // NVENC cannot adjust the given level, just throw an error.
 2033                }
 02034                else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02035                         || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02036                         || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 2037                {
 2038                    // level option may cause corrupted frames on AMD VAAPI.
 02039                    if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 2040                    {
 02041                        param += " -level " + level;
 2042                    }
 2043                }
 02044                else if (string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 02045                         || string.Equals(videoEncoder, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase))
 2046                {
 02047                    param += " -level " + level;
 2048                }
 02049                else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2050                {
 02051                    param += " -level " + level;
 2052                }
 2053            }
 2054
 02055            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2056            {
 02057                param += " -x264opts:0 subme=0:me_range=16:rc_lookahead=10:me=hex:open_gop=0";
 2058            }
 2059
 02060            if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2061            {
 2062                // libx265 only accept level option in -x265-params.
 2063                // level option may cause libx265 to fail.
 2064                // libx265 cannot adjust the given level, just throw an error.
 02065                param += " -x265-params:0 no-scenecut=1:no-open-gop=1:no-info=1";
 2066
 02067                if (encodingOptions.EncoderPreset < EncoderPreset.ultrafast)
 2068                {
 2069                    // The following params are slower than the ultrafast preset, don't use when ultrafast is selected.
 02070                    param += ":subme=3:merange=25:rc-lookahead=10:me=star:ctu=32:max-tu-size=32:min-cu-size=16:rskip=2:r
 2071                }
 2072            }
 2073
 02074            if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)
 02075                && _mediaEncoder.EncoderVersion >= _minFFmpegSvtAv1Params)
 2076            {
 02077                param += " -svtav1-params:0 rc=1:tune=0:film-grain=0:enable-overlays=1:enable-tf=0";
 2078            }
 2079
 2080            /* Access unit too large: 8192 < 20880 error */
 02081            if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) ||
 02082                 string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) &&
 02083                 _mediaEncoder.EncoderVersion >= _minFFmpegVaapiH26xEncA53CcSei)
 2084            {
 02085                param += " -sei -a53_cc";
 2086            }
 2087
 02088            return param;
 2089        }
 2090
 2091        public bool CanStreamCopyVideo(EncodingJobInfo state, MediaStream videoStream)
 2092        {
 02093            var request = state.BaseRequest;
 2094
 02095            if (!request.AllowVideoStreamCopy)
 2096            {
 02097                return false;
 2098            }
 2099
 02100            if (videoStream.IsInterlaced
 02101                && state.DeInterlace(videoStream.Codec, false))
 2102            {
 02103                return false;
 2104            }
 2105
 02106            if (videoStream.IsAnamorphic ?? false)
 2107            {
 02108                if (request.RequireNonAnamorphic)
 2109                {
 02110                    return false;
 2111                }
 2112            }
 2113
 2114            // Can't stream copy if we're burning in subtitles
 02115            if (request.SubtitleStreamIndex.HasValue
 02116                && request.SubtitleStreamIndex.Value >= 0
 02117                && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
 2118            {
 02119                return false;
 2120            }
 2121
 02122            if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 02123                && videoStream.IsAVC.HasValue
 02124                && !videoStream.IsAVC.Value
 02125                && request.RequireAvc)
 2126            {
 02127                return false;
 2128            }
 2129
 2130            // Source and target codecs must match
 02131            if (string.IsNullOrEmpty(videoStream.Codec)
 02132                || (state.SupportedVideoCodecs.Length != 0
 02133                    && !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparison.OrdinalIgnoreCase)))
 2134            {
 02135                return false;
 2136            }
 2137
 02138            var requestedProfiles = state.GetRequestedProfiles(videoStream.Codec);
 2139
 2140            // If client is requesting a specific video profile, it must match the source
 02141            if (requestedProfiles.Length > 0)
 2142            {
 02143                if (string.IsNullOrEmpty(videoStream.Profile))
 2144                {
 2145                    // return false;
 2146                }
 2147
 02148                var requestedProfile = requestedProfiles[0];
 2149                // strip spaces because they may be stripped out on the query string as well
 02150                if (!string.IsNullOrEmpty(videoStream.Profile)
 02151                    && !requestedProfiles.Contains(videoStream.Profile.Replace(" ", string.Empty, StringComparison.Ordin
 2152                {
 02153                    var currentScore = GetVideoProfileScore(videoStream.Codec, videoStream.Profile);
 02154                    var requestedScore = GetVideoProfileScore(videoStream.Codec, requestedProfile);
 2155
 02156                    if (currentScore == -1 || currentScore > requestedScore)
 2157                    {
 02158                        return false;
 2159                    }
 2160                }
 2161            }
 2162
 02163            var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
 02164            if (requestedRangeTypes.Length > 0)
 2165            {
 02166                if (videoStream.VideoRangeType == VideoRangeType.Unknown)
 2167                {
 02168                    return false;
 2169                }
 2170
 2171                // DOVIWithHDR10 should be compatible with HDR10 supporting players. Same goes with HLG and of course SD
 2172
 02173                var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ord
 02174                var requestHasHLG = requestedRangeTypes.Contains(VideoRangeType.HLG.ToString(), StringComparison.Ordinal
 02175                var requestHasSDR = requestedRangeTypes.Contains(VideoRangeType.SDR.ToString(), StringComparison.Ordinal
 2176
 02177                if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreC
 02178                     && !((requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10)
 02179                            || (requestHasHLG && videoStream.VideoRangeType == VideoRangeType.DOVIWithHLG)
 02180                            || (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)))
 2181                {
 02182                    return false;
 2183                }
 2184            }
 2185
 2186            // Video width must fall within requested value
 02187            if (request.MaxWidth.HasValue
 02188                && (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
 2189            {
 02190                return false;
 2191            }
 2192
 2193            // Video height must fall within requested value
 02194            if (request.MaxHeight.HasValue
 02195                && (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value))
 2196            {
 02197                return false;
 2198            }
 2199
 2200            // Video framerate must fall within requested value
 02201            var requestedFramerate = request.MaxFramerate ?? request.Framerate;
 02202            if (requestedFramerate.HasValue)
 2203            {
 02204                var videoFrameRate = videoStream.ReferenceFrameRate;
 2205
 2206                // Add a little tolerance to the framerate check because some videos might record a framerate
 2207                // that is slightly greater than the intended framerate, but the device can still play it correctly.
 2208                // 0.05 fps tolerance should be safe enough.
 02209                if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value + 0.05f)
 2210                {
 02211                    return false;
 2212                }
 2213            }
 2214
 2215            // Video bitrate must fall within requested value
 02216            if (request.VideoBitRate.HasValue
 02217                && (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value))
 2218            {
 2219                // For LiveTV that has no bitrate, let's try copy if other conditions are met
 02220                if (string.IsNullOrWhiteSpace(request.LiveStreamId) || videoStream.BitRate.HasValue)
 2221                {
 02222                    return false;
 2223                }
 2224            }
 2225
 02226            var maxBitDepth = state.GetRequestedVideoBitDepth(videoStream.Codec);
 02227            if (maxBitDepth.HasValue)
 2228            {
 02229                if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > maxBitDepth.Value)
 2230                {
 02231                    return false;
 2232                }
 2233            }
 2234
 02235            var maxRefFrames = state.GetRequestedMaxRefFrames(videoStream.Codec);
 02236            if (maxRefFrames.HasValue
 02237                && videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > maxRefFrames.Value)
 2238            {
 02239                return false;
 2240            }
 2241
 2242            // If a specific level was requested, the source must match or be less than
 02243            var level = state.GetRequestedLevel(videoStream.Codec);
 02244            if (double.TryParse(level, CultureInfo.InvariantCulture, out var requestLevel))
 2245            {
 02246                if (!videoStream.Level.HasValue)
 2247                {
 2248                    // return false;
 2249                }
 2250
 02251                if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
 2252                {
 02253                    return false;
 2254                }
 2255            }
 2256
 02257            if (string.Equals(state.InputContainer, "avi", StringComparison.OrdinalIgnoreCase)
 02258                && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)
 02259                && !(videoStream.IsAVC ?? false))
 2260            {
 2261                // see Coach S01E01 - Kelly and the Professor(0).avi
 02262                return false;
 2263            }
 2264
 02265            return true;
 2266        }
 2267
 2268        public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, IEnumerable<string> supportedAudi
 2269        {
 02270            var request = state.BaseRequest;
 2271
 02272            if (!request.AllowAudioStreamCopy)
 2273            {
 02274                return false;
 2275            }
 2276
 02277            var maxBitDepth = state.GetRequestedAudioBitDepth(audioStream.Codec);
 02278            if (maxBitDepth.HasValue
 02279                && audioStream.BitDepth.HasValue
 02280                && audioStream.BitDepth.Value > maxBitDepth.Value)
 2281            {
 02282                return false;
 2283            }
 2284
 2285            // Source and target codecs must match
 02286            if (string.IsNullOrEmpty(audioStream.Codec)
 02287                || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparison.OrdinalIgnoreCase))
 2288            {
 02289                return false;
 2290            }
 2291
 2292            // Channels must fall within requested value
 02293            var channels = state.GetRequestedAudioChannels(audioStream.Codec);
 02294            if (channels.HasValue)
 2295            {
 02296                if (!audioStream.Channels.HasValue || audioStream.Channels.Value <= 0)
 2297                {
 02298                    return false;
 2299                }
 2300
 02301                if (audioStream.Channels.Value > channels.Value)
 2302                {
 02303                    return false;
 2304                }
 2305            }
 2306
 2307            // Sample rate must fall within requested value
 02308            if (request.AudioSampleRate.HasValue)
 2309            {
 02310                if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value <= 0)
 2311                {
 02312                    return false;
 2313                }
 2314
 02315                if (audioStream.SampleRate.Value > request.AudioSampleRate.Value)
 2316                {
 02317                    return false;
 2318                }
 2319            }
 2320
 2321            // Audio bitrate must fall within requested value
 02322            if (request.AudioBitRate.HasValue
 02323                && audioStream.BitRate.HasValue
 02324                && audioStream.BitRate.Value > request.AudioBitRate.Value)
 2325            {
 02326                return false;
 2327            }
 2328
 02329            return request.EnableAutoStreamCopy;
 2330        }
 2331
 2332        public int GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideo
 2333        {
 02334            var bitrate = request.VideoBitRate;
 2335
 02336            if (videoStream is not null)
 2337            {
 02338                var isUpscaling = request.Height.HasValue
 02339                    && videoStream.Height.HasValue
 02340                    && request.Height.Value > videoStream.Height.Value
 02341                    && request.Width.HasValue
 02342                    && videoStream.Width.HasValue
 02343                    && request.Width.Value > videoStream.Width.Value;
 2344
 2345                // Don't allow bitrate increases unless upscaling
 02346                if (!isUpscaling && bitrate.HasValue && videoStream.BitRate.HasValue)
 2347                {
 02348                    bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value);
 2349                }
 2350
 02351                if (bitrate.HasValue)
 2352                {
 02353                    var inputVideoCodec = videoStream.Codec;
 02354                    bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
 2355
 2356                    // If a max bitrate was requested, don't let the scaled bitrate exceed it
 02357                    if (request.VideoBitRate.HasValue)
 2358                    {
 02359                        bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
 2360                    }
 2361                }
 2362            }
 2363
 2364            // Cap the max target bitrate to intMax/2 to satisfy the bufsize=bitrate*2.
 02365            return Math.Min(bitrate ?? 0, int.MaxValue / 2);
 2366        }
 2367
 2368        private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
 2369        {
 2370            // these values were chosen from testing to improve low bitrate streams
 02371            if (sourceBitrate <= 2000000)
 2372            {
 02373                sourceBitrate = Convert.ToInt32(sourceBitrate * 2.5);
 2374            }
 02375            else if (sourceBitrate <= 3000000)
 2376            {
 02377                sourceBitrate *= 2;
 2378            }
 2379
 02380            var bitrate = Math.Min(sourceBitrate, requestedBitrate);
 2381
 02382            return bitrate;
 2383        }
 2384
 2385        private static double GetVideoBitrateScaleFactor(string codec)
 2386        {
 2387            // hevc & vp9 - 40% more efficient than h.264
 02388            if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 02389                || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
 02390                || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
 2391            {
 02392                return .6;
 2393            }
 2394
 2395            // av1 - 50% more efficient than h.264
 02396            if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 2397            {
 02398                return .5;
 2399            }
 2400
 02401            return 1;
 2402        }
 2403
 2404        public static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
 2405        {
 02406            var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
 02407            var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
 2408
 2409            // Don't scale the real bitrate lower than the requested bitrate
 02410            var scaleFactor = Math.Max(outputScaleFactor / inputScaleFactor, 1);
 2411
 02412            if (bitrate <= 500000)
 2413            {
 02414                scaleFactor = Math.Max(scaleFactor, 4);
 2415            }
 02416            else if (bitrate <= 1000000)
 2417            {
 02418                scaleFactor = Math.Max(scaleFactor, 3);
 2419            }
 02420            else if (bitrate <= 2000000)
 2421            {
 02422                scaleFactor = Math.Max(scaleFactor, 2.5);
 2423            }
 02424            else if (bitrate <= 3000000)
 2425            {
 02426                scaleFactor = Math.Max(scaleFactor, 2);
 2427            }
 02428            else if (bitrate >= 30000000)
 2429            {
 2430                // Don't scale beyond 30Mbps, it is hardly visually noticeable for most codecs with our prefer speed enc
 2431                // and will cause extremely high bitrate to be used for av1->h264 transcoding that will overload clients
 02432                scaleFactor = 1;
 2433            }
 2434
 02435            return Convert.ToInt32(scaleFactor * bitrate);
 2436        }
 2437
 2438        public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChanne
 2439        {
 02440            return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels);
 2441        }
 2442
 2443        public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudio
 2444        {
 02445            if (audioStream is null)
 2446            {
 02447                return null;
 2448            }
 2449
 02450            var inputChannels = audioStream.Channels ?? 0;
 02451            var outputChannels = outputAudioChannels ?? 0;
 02452            var bitrate = audioBitRate ?? int.MaxValue;
 2453
 02454            if (string.IsNullOrEmpty(audioCodec)
 02455                || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
 02456                || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
 02457                || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
 02458                || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
 02459                || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
 02460                || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
 2461            {
 02462                return (inputChannels, outputChannels) switch
 02463                {
 02464                    (>= 6, >= 6 or 0) => Math.Min(640000, bitrate),
 02465                    (> 0, > 0) => Math.Min(outputChannels * 128000, bitrate),
 02466                    (> 0, _) => Math.Min(inputChannels * 128000, bitrate),
 02467                    (_, _) => Math.Min(384000, bitrate)
 02468                };
 2469            }
 2470
 02471            if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
 02472                || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
 2473            {
 02474                return (inputChannels, outputChannels) switch
 02475                {
 02476                    (>= 6, >= 6 or 0) => Math.Min(768000, bitrate),
 02477                    (> 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
 02478                    (> 0, _) => Math.Min(inputChannels * 136000, bitrate),
 02479                    (_, _) => Math.Min(672000, bitrate)
 02480                };
 2481            }
 2482
 2483            // Empty bitrate area is not allow on iOS
 2484            // Default audio bitrate to 128K per channel if we don't have codec specific defaults
 2485            // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
 02486            return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
 2487        }
 2488
 2489        public string GetAudioVbrModeParam(string encoder, int bitrate, int channels)
 2490        {
 02491            var bitratePerChannel = bitrate / Math.Max(channels, 1);
 02492            if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
 2493            {
 02494                return " -vbr:a " + bitratePerChannel switch
 02495                {
 02496                    < 32000 => "1",
 02497                    < 48000 => "2",
 02498                    < 64000 => "3",
 02499                    < 96000 => "4",
 02500                    _ => "5"
 02501                };
 2502            }
 2503
 02504            if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase))
 2505            {
 2506                // lame's VBR is only good for a certain bitrate range
 2507                // For very low and very high bitrate, use abr mode
 02508                if (bitratePerChannel is < 122500 and > 48000)
 2509                {
 02510                    return " -qscale:a " + bitratePerChannel switch
 02511                    {
 02512                        < 64000 => "6",
 02513                        < 88000 => "4",
 02514                        < 112000 => "2",
 02515                        _ => "0"
 02516                    };
 2517                }
 2518
 02519                return " -abr:a 1" + " -b:a " + bitrate;
 2520            }
 2521
 02522            if (string.Equals(encoder, "aac_at", StringComparison.OrdinalIgnoreCase))
 2523            {
 2524                // aac_at's CVBR mode
 02525                return " -aac_at_mode:a 2" + " -b:a " + bitrate;
 2526            }
 2527
 02528            if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase))
 2529            {
 02530                return " -qscale:a " + bitratePerChannel switch
 02531                {
 02532                    < 40000 => "0",
 02533                    < 56000 => "2",
 02534                    < 80000 => "4",
 02535                    < 112000 => "6",
 02536                    _ => "8"
 02537                };
 2538            }
 2539
 02540            return null;
 2541        }
 2542
 2543        public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
 2544        {
 02545            var channels = state.OutputAudioChannels;
 2546
 02547            var filters = new List<string>();
 2548
 02549            if (channels is 2 && state.AudioStream?.Channels is > 2)
 2550            {
 02551                var hasDownMixFilter = DownMixAlgorithmsHelper.AlgorithmFilterStrings.TryGetValue((encodingOptions.DownM
 02552                if (hasDownMixFilter)
 2553                {
 02554                    filters.Add(downMixFilterString);
 2555                }
 2556
 02557                if (!encodingOptions.DownMixAudioBoost.Equals(1))
 2558                {
 02559                    filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
 2560                }
 2561            }
 2562
 02563            var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
 02564            if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && ShouldEncodeSubtitle(st
 2565            {
 02566                var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds;
 2567
 02568                filters.Add(
 02569                    string.Format(
 02570                        CultureInfo.InvariantCulture,
 02571                        "asetpts=PTS-{0}/TB",
 02572                        Math.Round(seconds)));
 2573            }
 2574
 02575            if (filters.Count > 0)
 2576            {
 02577                return " -af \"" + string.Join(',', filters) + "\"";
 2578            }
 2579
 02580            return string.Empty;
 2581        }
 2582
 2583        /// <summary>
 2584        /// Gets the number of audio channels to specify on the command line.
 2585        /// </summary>
 2586        /// <param name="state">The state.</param>
 2587        /// <param name="audioStream">The audio stream.</param>
 2588        /// <param name="outputAudioCodec">The output audio codec.</param>
 2589        /// <returns>System.Nullable{System.Int32}.</returns>
 2590        public int? GetNumAudioChannelsParam(EncodingJobInfo state, MediaStream audioStream, string outputAudioCodec)
 2591        {
 02592            if (audioStream is null)
 2593            {
 02594                return null;
 2595            }
 2596
 02597            var request = state.BaseRequest;
 2598
 02599            var codec = outputAudioCodec ?? string.Empty;
 2600
 02601            int? resultChannels = state.GetRequestedAudioChannels(codec);
 2602
 02603            var inputChannels = audioStream.Channels;
 2604
 02605            if (inputChannels > 0)
 2606            {
 02607                resultChannels = inputChannels < resultChannels ? inputChannels : resultChannels ?? inputChannels;
 2608            }
 2609
 02610            var isTranscodingAudio = !IsCopyCodec(codec);
 2611
 02612            if (isTranscodingAudio)
 2613            {
 02614                var audioEncoder = GetAudioEncoder(state);
 02615                if (!_audioTranscodeChannelLookup.TryGetValue(audioEncoder, out var transcoderChannelLimit))
 2616                {
 2617                    // Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many c
 02618                    transcoderChannelLimit = 8;
 2619                }
 2620
 2621                // Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelL
 02622                resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? tr
 2623
 02624                if (request.TranscodingMaxAudioChannels < resultChannels)
 2625                {
 02626                    resultChannels = request.TranscodingMaxAudioChannels;
 2627                }
 2628
 2629                // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
 2630                // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_d
 02631                if (state.TranscodingType != TranscodingJobType.Progressive
 02632                    && ((resultChannels > 2 && resultChannels < 6) || resultChannels == 7))
 2633                {
 2634                    // We can let FFMpeg supply an extra LFE channel for 5ch and 7ch to make them 5.1 and 7.1
 02635                    if (resultChannels == 5)
 2636                    {
 02637                        resultChannels = 6;
 2638                    }
 02639                    else if (resultChannels == 7)
 2640                    {
 02641                        resultChannels = 8;
 2642                    }
 2643                    else
 2644                    {
 2645                        // For other weird layout, just downmix to stereo for compatibility
 02646                        resultChannels = 2;
 2647                    }
 2648                }
 2649            }
 2650
 02651            return resultChannels;
 2652        }
 2653
 2654        /// <summary>
 2655        /// Enforces the resolution limit.
 2656        /// </summary>
 2657        /// <param name="state">The state.</param>
 2658        public void EnforceResolutionLimit(EncodingJobInfo state)
 2659        {
 02660            var videoRequest = state.BaseRequest;
 2661
 2662            // Switch the incoming params to be ceilings rather than fixed values
 02663            videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
 02664            videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
 2665
 02666            videoRequest.Width = null;
 02667            videoRequest.Height = null;
 02668        }
 2669
 2670        /// <summary>
 2671        /// Gets the fast seek command line parameter.
 2672        /// </summary>
 2673        /// <param name="state">The state.</param>
 2674        /// <param name="options">The options.</param>
 2675        /// <param name="segmentContainer">Segment Container.</param>
 2676        /// <returns>System.String.</returns>
 2677        /// <value>The fast seek command line parameter.</value>
 2678        public string GetFastSeekCommandLineParameter(EncodingJobInfo state, EncodingOptions options, string segmentCont
 2679        {
 02680            var time = state.BaseRequest.StartTimeTicks ?? 0;
 02681            var maxTime = state.RunTimeTicks ?? 0;
 02682            var seekParam = string.Empty;
 2683
 02684            if (time > 0)
 2685            {
 2686                // For direct streaming/remuxing, we seek at the exact position of the keyframe
 2687                // However, ffmpeg will seek to previous keyframe when the exact time is the input
 2688                // Workaround this by adding 0.5s offset to the seeking time to get the exact keyframe on most videos.
 2689                // This will help subtitle syncing.
 02690                var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCod
 02691                var seekTick = isHlsRemuxing ? time + 5000000L : time;
 2692
 2693                // Seeking beyond EOF makes no sense in transcoding. Clamp the seekTick value to
 2694                // [0, RuntimeTicks - 0.5s], so that the muxer gets packets and avoid error codes.
 02695                if (maxTime > 0)
 2696                {
 02697                    seekTick = Math.Clamp(seekTick, 0, Math.Max(maxTime - 5000000L, 0));
 2698                }
 2699
 02700                seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(seekT
 2701
 02702                if (state.IsVideoRequest)
 2703                {
 02704                    var outputVideoCodec = GetVideoEncoder(state, options);
 02705                    var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
 2706
 2707                    // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking
 2708                    // Disable -noaccurate_seek on mpegts container due to the timestamps issue on some clients,
 2709                    // but it's still required for fMP4 container otherwise the audio can't be synced to the video.
 02710                    if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase)
 02711                        && !string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase)
 02712                        && state.TranscodingType != TranscodingJobType.Progressive
 02713                        && !state.EnableBreakOnNonKeyFrames(outputVideoCodec)
 02714                        && (state.BaseRequest.StartTimeTicks ?? 0) > 0)
 2715                    {
 02716                        seekParam += " -noaccurate_seek";
 2717                    }
 2718                }
 2719            }
 2720
 02721            return seekParam;
 2722        }
 2723
 2724        /// <summary>
 2725        /// Gets the map args.
 2726        /// </summary>
 2727        /// <param name="state">The state.</param>
 2728        /// <returns>System.String.</returns>
 2729        public string GetMapArgs(EncodingJobInfo state)
 2730        {
 2731            // If we don't have known media info
 2732            // If input is video, use -sn to drop subtitles
 2733            // Otherwise just return empty
 02734            if (state.VideoStream is null && state.AudioStream is null)
 2735            {
 02736                return state.IsInputVideo ? "-sn" : string.Empty;
 2737            }
 2738
 2739            // We have media info, but we don't know the stream index
 02740            if (state.VideoStream is not null && state.VideoStream.Index == -1)
 2741            {
 02742                return "-sn";
 2743            }
 2744
 2745            // We have media info, but we don't know the stream index
 02746            if (state.AudioStream is not null && state.AudioStream.Index == -1)
 2747            {
 02748                return state.IsInputVideo ? "-sn" : string.Empty;
 2749            }
 2750
 02751            var args = string.Empty;
 2752
 02753            if (state.VideoStream is not null)
 2754            {
 02755                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 2756
 02757                args += string.Format(
 02758                    CultureInfo.InvariantCulture,
 02759                    "-map 0:{0}",
 02760                    videoStreamIndex);
 2761            }
 2762            else
 2763            {
 2764                // No known video stream
 02765                args += "-vn";
 2766            }
 2767
 02768            if (state.AudioStream is not null)
 2769            {
 02770                int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
 02771                if (state.AudioStream.IsExternal)
 2772                {
 02773                    bool hasExternalGraphicsSubs = state.SubtitleStream is not null
 02774                        && ShouldEncodeSubtitle(state)
 02775                        && state.SubtitleStream.IsExternal
 02776                        && !state.SubtitleStream.IsTextSubtitleStream;
 02777                    int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
 2778
 02779                    args += string.Format(
 02780                        CultureInfo.InvariantCulture,
 02781                        " -map {0}:{1}",
 02782                        externalAudioMapIndex,
 02783                        audioStreamIndex);
 2784                }
 2785                else
 2786                {
 02787                    args += string.Format(
 02788                        CultureInfo.InvariantCulture,
 02789                        " -map 0:{0}",
 02790                        audioStreamIndex);
 2791                }
 2792            }
 2793            else
 2794            {
 02795                args += " -map -0:a";
 2796            }
 2797
 02798            var subtitleMethod = state.SubtitleDeliveryMethod;
 02799            if (state.SubtitleStream is null || subtitleMethod == SubtitleDeliveryMethod.Hls)
 2800            {
 02801                args += " -map -0:s";
 2802            }
 02803            else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
 2804            {
 02805                int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 2806
 02807                args += string.Format(
 02808                    CultureInfo.InvariantCulture,
 02809                    " -map 0:{0}",
 02810                    subtitleStreamIndex);
 2811            }
 02812            else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
 2813            {
 02814                int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 2815
 02816                args += string.Format(
 02817                    CultureInfo.InvariantCulture,
 02818                    " -map 1:{0} -sn",
 02819                    externalSubtitleStreamIndex);
 2820            }
 2821
 02822            return args;
 2823        }
 2824
 2825        /// <summary>
 2826        /// Gets the negative map args by filters.
 2827        /// </summary>
 2828        /// <param name="state">The state.</param>
 2829        /// <param name="videoProcessFilters">The videoProcessFilters.</param>
 2830        /// <returns>System.String.</returns>
 2831        public string GetNegativeMapArgsByFilters(EncodingJobInfo state, string videoProcessFilters)
 2832        {
 02833            string args = string.Empty;
 2834
 2835            // http://ffmpeg.org/ffmpeg-all.html#toc-Complex-filtergraphs-1
 02836            if (state.VideoStream is not null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordina
 2837            {
 02838                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 2839
 02840                args += string.Format(
 02841                    CultureInfo.InvariantCulture,
 02842                    "-map -0:{0} ",
 02843                    videoStreamIndex);
 2844            }
 2845
 02846            return args;
 2847        }
 2848
 2849        /// <summary>
 2850        /// Determines which stream will be used for playback.
 2851        /// </summary>
 2852        /// <param name="allStream">All stream.</param>
 2853        /// <param name="desiredIndex">Index of the desired.</param>
 2854        /// <param name="type">The type.</param>
 2855        /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
 2856        /// <returns>MediaStream.</returns>
 2857        public MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, b
 2858        {
 02859            var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
 2860
 02861            if (desiredIndex.HasValue)
 2862            {
 02863                var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
 2864
 02865                if (stream is not null)
 2866                {
 02867                    return stream;
 2868                }
 2869            }
 2870
 02871            if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
 2872            {
 02873                return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
 02874                       streams.FirstOrDefault();
 2875            }
 2876
 2877            // Just return the first one
 02878            return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
 2879        }
 2880
 2881        public static (int? Width, int? Height) GetFixedOutputSize(
 2882            int? videoWidth,
 2883            int? videoHeight,
 2884            int? requestedWidth,
 2885            int? requestedHeight,
 2886            int? requestedMaxWidth,
 2887            int? requestedMaxHeight)
 2888        {
 02889            if (!videoWidth.HasValue && !requestedWidth.HasValue)
 2890            {
 02891                return (null, null);
 2892            }
 2893
 02894            if (!videoHeight.HasValue && !requestedHeight.HasValue)
 2895            {
 02896                return (null, null);
 2897            }
 2898
 02899            int inputWidth = Convert.ToInt32(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture);
 02900            int inputHeight = Convert.ToInt32(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture);
 02901            int outputWidth = requestedWidth ?? inputWidth;
 02902            int outputHeight = requestedHeight ?? inputHeight;
 2903
 2904            // Don't transcode video to bigger than 4k when using HW.
 02905            int maximumWidth = Math.Min(requestedMaxWidth ?? outputWidth, 4096);
 02906            int maximumHeight = Math.Min(requestedMaxHeight ?? outputHeight, 4096);
 2907
 02908            if (outputWidth > maximumWidth || outputHeight > maximumHeight)
 2909            {
 02910                var scaleW = (double)maximumWidth / outputWidth;
 02911                var scaleH = (double)maximumHeight / outputHeight;
 02912                var scale = Math.Min(scaleW, scaleH);
 02913                outputWidth = Math.Min(maximumWidth, Convert.ToInt32(outputWidth * scale));
 02914                outputHeight = Math.Min(maximumHeight, Convert.ToInt32(outputHeight * scale));
 2915            }
 2916
 02917            outputWidth = 2 * (outputWidth / 2);
 02918            outputHeight = 2 * (outputHeight / 2);
 2919
 02920            return (outputWidth, outputHeight);
 2921        }
 2922
 2923        public static bool IsScaleRatioSupported(
 2924            int? videoWidth,
 2925            int? videoHeight,
 2926            int? requestedWidth,
 2927            int? requestedHeight,
 2928            int? requestedMaxWidth,
 2929            int? requestedMaxHeight,
 2930            double? maxScaleRatio)
 2931        {
 02932            var (outWidth, outHeight) = GetFixedOutputSize(
 02933                videoWidth,
 02934                videoHeight,
 02935                requestedWidth,
 02936                requestedHeight,
 02937                requestedMaxWidth,
 02938                requestedMaxHeight);
 2939
 02940            if (!videoWidth.HasValue
 02941                 || !videoHeight.HasValue
 02942                 || !outWidth.HasValue
 02943                 || !outHeight.HasValue
 02944                 || !maxScaleRatio.HasValue
 02945                 || (maxScaleRatio.Value < 1.0f))
 2946            {
 02947                return false;
 2948            }
 2949
 02950            var minScaleRatio = 1.0f / maxScaleRatio;
 02951            var scaleRatioW = (double)outWidth / (double)videoWidth;
 02952            var scaleRatioH = (double)outHeight / (double)videoHeight;
 2953
 02954            if (scaleRatioW < minScaleRatio
 02955                || scaleRatioW > maxScaleRatio
 02956                || scaleRatioH < minScaleRatio
 02957                || scaleRatioH > maxScaleRatio)
 2958            {
 02959                return false;
 2960            }
 2961
 02962            return true;
 2963        }
 2964
 2965        public static string GetHwScaleFilter(
 2966            string hwScalePrefix,
 2967            string hwScaleSuffix,
 2968            string videoFormat,
 2969            bool swapOutputWandH,
 2970            int? videoWidth,
 2971            int? videoHeight,
 2972            int? requestedWidth,
 2973            int? requestedHeight,
 2974            int? requestedMaxWidth,
 2975            int? requestedMaxHeight)
 2976        {
 02977            var (outWidth, outHeight) = GetFixedOutputSize(
 02978                videoWidth,
 02979                videoHeight,
 02980                requestedWidth,
 02981                requestedHeight,
 02982                requestedMaxWidth,
 02983                requestedMaxHeight);
 2984
 02985            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 02986            var isSizeFixed = !videoWidth.HasValue
 02987                || outWidth.Value != videoWidth.Value
 02988                || !videoHeight.HasValue
 02989                || outHeight.Value != videoHeight.Value;
 2990
 02991            var swpOutW = swapOutputWandH ? outHeight.Value : outWidth.Value;
 02992            var swpOutH = swapOutputWandH ? outWidth.Value : outHeight.Value;
 2993
 02994            var arg1 = isSizeFixed ? $"=w={swpOutW}:h={swpOutH}" : string.Empty;
 02995            var arg2 = isFormatFixed ? $"format={videoFormat}" : string.Empty;
 02996            if (isFormatFixed)
 2997            {
 02998                arg2 = (isSizeFixed ? ':' : '=') + arg2;
 2999            }
 3000
 03001            if (!string.IsNullOrEmpty(hwScaleSuffix) && (isSizeFixed || isFormatFixed))
 3002            {
 03003                return string.Format(
 03004                    CultureInfo.InvariantCulture,
 03005                    "{0}_{1}{2}{3}",
 03006                    hwScalePrefix ?? "scale",
 03007                    hwScaleSuffix,
 03008                    arg1,
 03009                    arg2);
 3010            }
 3011
 03012            return string.Empty;
 3013        }
 3014
 3015        public static string GetGraphicalSubPreProcessFilters(
 3016            int? videoWidth,
 3017            int? videoHeight,
 3018            int? subtitleWidth,
 3019            int? subtitleHeight,
 3020            int? requestedWidth,
 3021            int? requestedHeight,
 3022            int? requestedMaxWidth,
 3023            int? requestedMaxHeight)
 3024        {
 03025            var (outWidth, outHeight) = GetFixedOutputSize(
 03026                videoWidth,
 03027                videoHeight,
 03028                requestedWidth,
 03029                requestedHeight,
 03030                requestedMaxWidth,
 03031                requestedMaxHeight);
 3032
 03033            if (!outWidth.HasValue
 03034                || !outHeight.HasValue
 03035                || outWidth.Value <= 0
 03036                || outHeight.Value <= 0)
 3037            {
 03038                return string.Empty;
 3039            }
 3040
 3041            // Automatically add padding based on subtitle input
 03042            var filters = @"scale,scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:blac
 3043
 03044            if (subtitleWidth.HasValue
 03045                && subtitleHeight.HasValue
 03046                && subtitleWidth.Value > 0
 03047                && subtitleHeight.Value > 0)
 3048            {
 03049                var videoDar = (double)outWidth.Value / outHeight.Value;
 03050                var subtitleDar = (double)subtitleWidth.Value / subtitleHeight.Value;
 3051
 3052                // No need to add padding when DAR is the same -> 1080p PGSSUB on 2160p video
 03053                if (Math.Abs(videoDar - subtitleDar) < 0.01f)
 3054                {
 03055                    filters = @"scale,scale={0}:{1}:fast_bilinear";
 3056                }
 3057            }
 3058
 03059            return string.Format(
 03060                CultureInfo.InvariantCulture,
 03061                filters,
 03062                outWidth.Value,
 03063                outHeight.Value);
 3064        }
 3065
 3066        public static string GetAlphaSrcFilter(
 3067            EncodingJobInfo state,
 3068            int? videoWidth,
 3069            int? videoHeight,
 3070            int? requestedWidth,
 3071            int? requestedHeight,
 3072            int? requestedMaxWidth,
 3073            int? requestedMaxHeight,
 3074            float? framerate)
 3075        {
 03076            var reqTicks = state.BaseRequest.StartTimeTicks ?? 0;
 03077            var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture
 03078            var (outWidth, outHeight) = GetFixedOutputSize(
 03079                videoWidth,
 03080                videoHeight,
 03081                requestedWidth,
 03082                requestedHeight,
 03083                requestedMaxWidth,
 03084                requestedMaxHeight);
 3085
 03086            if (outWidth.HasValue && outHeight.HasValue)
 3087            {
 03088                return string.Format(
 03089                    CultureInfo.InvariantCulture,
 03090                    "alphasrc=s={0}x{1}:r={2}:start='{3}'",
 03091                    outWidth.Value,
 03092                    outHeight.Value,
 03093                    framerate ?? 25,
 03094                    reqTicks > 0 ? startTime : 0);
 3095            }
 3096
 03097            return string.Empty;
 3098        }
 3099
 3100        public static string GetSwScaleFilter(
 3101            EncodingJobInfo state,
 3102            EncodingOptions options,
 3103            string videoEncoder,
 3104            int? videoWidth,
 3105            int? videoHeight,
 3106            Video3DFormat? threedFormat,
 3107            int? requestedWidth,
 3108            int? requestedHeight,
 3109            int? requestedMaxWidth,
 3110            int? requestedMaxHeight)
 3111        {
 03112            var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 03113            var isMjpeg = videoEncoder is not null && videoEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase)
 03114            var scaleVal = isV4l2 ? 64 : 2;
 03115            var targetAr = isMjpeg ? "(a*sar)" : "a"; // manually calculate AR when using mjpeg encoder
 3116
 3117            // If fixed dimensions were supplied
 03118            if (requestedWidth.HasValue && requestedHeight.HasValue)
 3119            {
 03120                if (isV4l2)
 3121                {
 03122                    var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 03123                    var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3124
 03125                    return string.Format(
 03126                            CultureInfo.InvariantCulture,
 03127                            "scale=trunc({0}/64)*64:trunc({1}/2)*2",
 03128                            widthParam,
 03129                            heightParam);
 3130                }
 3131
 03132                return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
 3133            }
 3134
 3135            // If Max dimensions were supplied, for width selects lowest even number between input width and width req s
 3136
 03137            if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
 3138            {
 03139                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 03140                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3141
 03142                return string.Format(
 03143                    CultureInfo.InvariantCulture,
 03144                    @"scale=trunc(min(max(iw\,ih*{3})\,min({0}\,{1}*{3}))/{2})*{2}:trunc(min(max(iw/{3}\,ih)\,min({0}/{3
 03145                    maxWidthParam,
 03146                    maxHeightParam,
 03147                    scaleVal,
 03148                    targetAr);
 3149            }
 3150
 3151            // If a fixed width was requested
 03152            if (requestedWidth.HasValue)
 3153            {
 03154                if (threedFormat.HasValue)
 3155                {
 3156                    // This method can handle 0 being passed in for the requested height
 03157                    return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0);
 3158                }
 3159
 03160                var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 3161
 03162                return string.Format(
 03163                    CultureInfo.InvariantCulture,
 03164                    "scale={0}:trunc(ow/{1}/2)*2",
 03165                    widthParam,
 03166                    targetAr);
 3167            }
 3168
 3169            // If a fixed height was requested
 03170            if (requestedHeight.HasValue)
 3171            {
 03172                var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3173
 03174                return string.Format(
 03175                    CultureInfo.InvariantCulture,
 03176                    "scale=trunc(oh*{2}/{1})*{1}:{0}",
 03177                    heightParam,
 03178                    scaleVal,
 03179                    targetAr);
 3180            }
 3181
 3182            // If a max width was requested
 03183            if (requestedMaxWidth.HasValue)
 3184            {
 03185                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 3186
 03187                return string.Format(
 03188                    CultureInfo.InvariantCulture,
 03189                    @"scale=trunc(min(max(iw\,ih*{2})\,{0})/{1})*{1}:trunc(ow/{2}/2)*2",
 03190                    maxWidthParam,
 03191                    scaleVal,
 03192                    targetAr);
 3193            }
 3194
 3195            // If a max height was requested
 03196            if (requestedMaxHeight.HasValue)
 3197            {
 03198                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3199
 03200                return string.Format(
 03201                    CultureInfo.InvariantCulture,
 03202                    @"scale=trunc(oh*{2}/{1})*{1}:min(max(iw/{2}\,ih)\,{0})",
 03203                    maxHeightParam,
 03204                    scaleVal,
 03205                    targetAr);
 3206            }
 3207
 03208            return string.Empty;
 3209        }
 3210
 3211        private static string GetFixedSwScaleFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight
 3212        {
 03213            var widthParam = requestedWidth.ToString(CultureInfo.InvariantCulture);
 03214            var heightParam = requestedHeight.ToString(CultureInfo.InvariantCulture);
 3215
 03216            string filter = null;
 3217
 03218            if (threedFormat.HasValue)
 3219            {
 03220                switch (threedFormat.Value)
 3221                {
 3222                    case Video3DFormat.HalfSideBySide:
 03223                        filter = @"crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(i
 3224                        // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars
 03225                        break;
 3226                    case Video3DFormat.FullSideBySide:
 03227                        filter = @"crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3228                        // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the s
 03229                        break;
 3230                    case Video3DFormat.HalfTopAndBottom:
 03231                        filter = @"crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(
 3232                        // htab crop height in half,scale to correct size, set the display aspect,crop out any black bar
 03233                        break;
 3234                    case Video3DFormat.FullTopAndBottom:
 03235                        filter = @"crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3236                        // ftab crop height in half, set the display aspect,crop out any black bars we may have made the
 3237                        break;
 3238                    default:
 3239                        break;
 3240                }
 3241            }
 3242
 3243            // default
 03244            if (filter is null)
 3245            {
 03246                if (requestedHeight > 0)
 3247                {
 03248                    filter = "scale=trunc({0}/2)*2:trunc({1}/2)*2";
 3249                }
 3250                else
 3251                {
 03252                    filter = "scale={0}:trunc({0}/a/2)*2";
 3253                }
 3254            }
 3255
 03256            return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam);
 3257        }
 3258
 3259        public static string GetSwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options)
 3260        {
 03261            var doubleRateDeint = options.DeinterlaceDoubleRate && state.VideoStream?.ReferenceFrameRate <= 30;
 03262            return string.Format(
 03263                CultureInfo.InvariantCulture,
 03264                "{0}={1}:-1:0",
 03265                options.DeinterlaceMethod.ToString().ToLowerInvariant(),
 03266                doubleRateDeint ? "1" : "0");
 3267        }
 3268
 3269        public string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix)
 3270        {
 03271            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03272            if (hwDeintSuffix.Contains("cuda", StringComparison.OrdinalIgnoreCase))
 3273            {
 03274                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3275
 03276                return string.Format(
 03277                    CultureInfo.InvariantCulture,
 03278                    "{0}_cuda={1}:-1:0",
 03279                    useBwdif ? "bwdif" : "yadif",
 03280                    doubleRateDeint ? "1" : "0");
 3281            }
 3282
 03283            if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
 3284            {
 03285                return string.Format(
 03286                    CultureInfo.InvariantCulture,
 03287                    "deinterlace_vaapi=rate={0}",
 03288                    doubleRateDeint ? "field" : "frame");
 3289            }
 3290
 03291            if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
 3292            {
 03293                return "deinterlace_qsv=mode=2";
 3294            }
 3295
 03296            if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
 3297            {
 03298                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3299
 03300                return string.Format(
 03301                    CultureInfo.InvariantCulture,
 03302                    "{0}_videotoolbox={1}:-1:0",
 03303                    useBwdif ? "bwdif" : "yadif",
 03304                    doubleRateDeint ? "1" : "0");
 3305            }
 3306
 03307            return string.Empty;
 3308        }
 3309
 3310        private string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat, bool forc
 3311        {
 03312            if (string.IsNullOrEmpty(hwTonemapSuffix))
 3313            {
 03314                return string.Empty;
 3315            }
 3316
 03317            var args = string.Empty;
 03318            var algorithm = options.TonemappingAlgorithm.ToString().ToLowerInvariant();
 03319            var mode = options.TonemappingMode.ToString().ToLowerInvariant();
 03320            var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 03321            var rangeString = range.ToString().ToLowerInvariant();
 3322
 03323            if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
 3324            {
 03325                var doVaVppProcamp = false;
 03326                var procampParams = string.Empty;
 03327                if (options.VppTonemappingBrightness != 0
 03328                    && options.VppTonemappingBrightness >= -100
 03329                    && options.VppTonemappingBrightness <= 100)
 3330                {
 03331                    procampParams += "procamp_vaapi=b={0}";
 03332                    doVaVppProcamp = true;
 3333                }
 3334
 03335                if (options.VppTonemappingContrast > 1
 03336                    && options.VppTonemappingContrast <= 10)
 3337                {
 03338                    procampParams += doVaVppProcamp ? ":c={1}" : "procamp_vaapi=c={1}";
 03339                    doVaVppProcamp = true;
 3340                }
 3341
 03342                args = procampParams + "{2}tonemap_vaapi=format={3}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
 3343
 03344                return string.Format(
 03345                        CultureInfo.InvariantCulture,
 03346                        args,
 03347                        options.VppTonemappingBrightness,
 03348                        options.VppTonemappingContrast,
 03349                        doVaVppProcamp ? "," : string.Empty,
 03350                        videoFormat ?? "nv12");
 3351            }
 3352            else
 3353            {
 03354                args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
 3355
 03356                var useLegacyTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode
 03357                                           && _legacyTonemapModes.Contains(options.TonemappingMode);
 3358
 03359                var useAdvancedTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegAdvancedTonemapMode
 03360                                              && _advancedTonemapModes.Contains(options.TonemappingMode);
 3361
 03362                if (useLegacyTonemapModes || useAdvancedTonemapModes)
 3363                {
 03364                    args += ":tonemap_mode={5}";
 3365                }
 3366
 03367                if (options.TonemappingParam != 0)
 3368                {
 03369                    args += ":param={6}";
 3370                }
 3371
 03372                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3373                {
 03374                    args += ":range={7}";
 3375                }
 3376            }
 3377
 03378            return string.Format(
 03379                    CultureInfo.InvariantCulture,
 03380                    args,
 03381                    hwTonemapSuffix,
 03382                    videoFormat ?? "nv12",
 03383                    algorithm,
 03384                    options.TonemappingPeak,
 03385                    options.TonemappingDesat,
 03386                    mode,
 03387                    options.TonemappingParam,
 03388                    rangeString);
 3389        }
 3390
 3391        private string GetLibplaceboFilter(
 3392            EncodingOptions options,
 3393            string videoFormat,
 3394            bool doTonemap,
 3395            int? videoWidth,
 3396            int? videoHeight,
 3397            int? requestedWidth,
 3398            int? requestedHeight,
 3399            int? requestedMaxWidth,
 3400            int? requestedMaxHeight,
 3401            bool forceFullRange)
 3402        {
 03403            var (outWidth, outHeight) = GetFixedOutputSize(
 03404                videoWidth,
 03405                videoHeight,
 03406                requestedWidth,
 03407                requestedHeight,
 03408                requestedMaxWidth,
 03409                requestedMaxHeight);
 3410
 03411            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03412            var isSizeFixed = !videoWidth.HasValue
 03413                || outWidth.Value != videoWidth.Value
 03414                || !videoHeight.HasValue
 03415                || outHeight.Value != videoHeight.Value;
 3416
 03417            var sizeArg = isSizeFixed ? (":w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty;
 03418            var formatArg = isFormatFixed ? (":format=" + videoFormat) : string.Empty;
 03419            var tonemapArg = string.Empty;
 3420
 03421            if (doTonemap)
 3422            {
 03423                var algorithm = options.TonemappingAlgorithm;
 03424                var algorithmString = "clip";
 03425                var mode = options.TonemappingMode;
 03426                var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 3427
 03428                if (algorithm == TonemappingAlgorithm.bt2390)
 3429                {
 03430                    algorithmString = "bt.2390";
 3431                }
 03432                else if (algorithm != TonemappingAlgorithm.none)
 3433                {
 03434                    algorithmString = algorithm.ToString().ToLowerInvariant();
 3435                }
 3436
 03437                tonemapArg = $":tonemapping={algorithmString}:peak_detect=0:color_primaries=bt709:color_trc=bt709:colors
 3438
 03439                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3440                {
 03441                    tonemapArg += ":range=" + range.ToString().ToLowerInvariant();
 3442                }
 3443            }
 3444
 03445            return string.Format(
 03446                CultureInfo.InvariantCulture,
 03447                "libplacebo=upscaler=none:downscaler=none{0}{1}{2}",
 03448                sizeArg,
 03449                formatArg,
 03450                tonemapArg);
 3451        }
 3452
 3453        public string GetVideoTransposeDirection(EncodingJobInfo state)
 3454        {
 03455            return (state.VideoStream?.Rotation ?? 0) switch
 03456            {
 03457                90 => "cclock",
 03458                180 => "reversal",
 03459                -90 => "clock",
 03460                -180 => "reversal",
 03461                _ => string.Empty
 03462            };
 3463        }
 3464
 3465        /// <summary>
 3466        /// Gets the parameter of software filter chain.
 3467        /// </summary>
 3468        /// <param name="state">Encoding state.</param>
 3469        /// <param name="options">Encoding options.</param>
 3470        /// <param name="vidEncoder">Video encoder to use.</param>
 3471        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3472        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetSwVidFilterChain(
 3473            EncodingJobInfo state,
 3474            EncodingOptions options,
 3475            string vidEncoder)
 3476        {
 03477            var inW = state.VideoStream?.Width;
 03478            var inH = state.VideoStream?.Height;
 03479            var reqW = state.BaseRequest.Width;
 03480            var reqH = state.BaseRequest.Height;
 03481            var reqMaxW = state.BaseRequest.MaxWidth;
 03482            var reqMaxH = state.BaseRequest.MaxHeight;
 03483            var threeDFormat = state.MediaSource.Video3DFormat;
 3484
 03485            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03486            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03487            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 03488            var isV4l2Encoder = vidEncoder.Contains("h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 3489
 03490            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03491            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03492            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03493            var doToneMap = IsSwTonemapAvailable(state, options);
 03494            var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI;
 3495
 03496            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03497            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03498            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 3499
 03500            var rotation = state.VideoStream?.Rotation ?? 0;
 03501            var swapWAndH = Math.Abs(rotation) == 90;
 03502            var swpInW = swapWAndH ? inH : inW;
 03503            var swpInH = swapWAndH ? inW : inH;
 3504
 3505            /* Make main filters for video stream */
 03506            var mainFilters = new List<string>();
 3507
 03508            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doToneMap));
 3509
 3510            // INPUT sw surface(memory/copy-back from vram)
 3511            // sw deint
 03512            if (doDeintH2645)
 3513            {
 03514                var deintFilter = GetSwDeinterlaceFilter(state, options);
 03515                mainFilters.Add(deintFilter);
 3516            }
 3517
 03518            var outFormat = isSwDecoder ? "yuv420p" : "nv12";
 03519            var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, r
 03520            if (isVaapiEncoder)
 3521            {
 03522                outFormat = "nv12";
 3523            }
 03524            else if (isV4l2Encoder)
 3525            {
 03526                outFormat = "yuv420p";
 3527            }
 3528
 3529            // sw scale
 03530            mainFilters.Add(swScaleFilter);
 3531
 3532            // sw tonemap
 03533            if (doToneMap)
 3534            {
 3535                // tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary
 03536                var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat;
 03537                var tonemapArgString = "tonemapx=tonemap={0}:desat={1}:peak={2}:t=bt709:m=bt709:p=bt709:format={3}";
 3538
 03539                if (options.TonemappingParam != 0)
 3540                {
 03541                    tonemapArgString += ":param={4}";
 3542                }
 3543
 03544                var range = options.TonemappingRange;
 03545                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3546                {
 03547                    tonemapArgString += ":range={5}";
 3548                }
 3549
 03550                var tonemapArgs = string.Format(
 03551                    CultureInfo.InvariantCulture,
 03552                    tonemapArgString,
 03553                    options.TonemappingAlgorithm,
 03554                    options.TonemappingDesat,
 03555                    options.TonemappingPeak,
 03556                    tonemapFormat,
 03557                    options.TonemappingParam,
 03558                    options.TonemappingRange);
 3559
 03560                mainFilters.Add(tonemapArgs);
 3561            }
 3562            else
 3563            {
 3564                // OUTPUT yuv420p/nv12 surface(memory)
 03565                mainFilters.Add("format=" + outFormat);
 3566            }
 3567
 3568            /* Make sub and overlay filters for subtitle stream */
 03569            var subFilters = new List<string>();
 03570            var overlayFilters = new List<string>();
 03571            if (hasTextSubs)
 3572            {
 3573                // subtitles=f='*.ass':alpha=0
 03574                var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03575                mainFilters.Add(textSubtitlesFilter);
 3576            }
 03577            else if (hasGraphicalSubs)
 3578            {
 03579                var subW = state.SubtitleStream?.Width;
 03580                var subH = state.SubtitleStream?.Height;
 03581                var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW
 03582                subFilters.Add(subPreProcFilters);
 03583                overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 3584            }
 3585
 03586            return (mainFilters, subFilters, overlayFilters);
 3587        }
 3588
 3589        /// <summary>
 3590        /// Gets the parameter of Nvidia NVENC filter chain.
 3591        /// </summary>
 3592        /// <param name="state">Encoding state.</param>
 3593        /// <param name="options">Encoding options.</param>
 3594        /// <param name="vidEncoder">Video encoder to use.</param>
 3595        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3596        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFilterChain(
 3597            EncodingJobInfo state,
 3598            EncodingOptions options,
 3599            string vidEncoder)
 3600        {
 03601            if (options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 3602            {
 03603                return (null, null, null);
 3604            }
 3605
 03606            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03607            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03608            var isSwEncoder = !vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 3609
 3610            // legacy cuvid pipeline(copy-back)
 03611            if ((isSwDecoder && isSwEncoder)
 03612                || !IsCudaFullSupported()
 03613                || !_mediaEncoder.SupportsFilter("alphasrc"))
 3614            {
 03615                return GetSwVidFilterChain(state, options, vidEncoder);
 3616            }
 3617
 3618            // preferred nvdec/cuvid + cuda filters + nvenc pipeline
 03619            return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 3620        }
 3621
 3622        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFiltersPrefe
 3623            EncodingJobInfo state,
 3624            EncodingOptions options,
 3625            string vidDecoder,
 3626            string vidEncoder)
 3627        {
 03628            var inW = state.VideoStream?.Width;
 03629            var inH = state.VideoStream?.Height;
 03630            var reqW = state.BaseRequest.Width;
 03631            var reqH = state.BaseRequest.Height;
 03632            var reqMaxW = state.BaseRequest.MaxWidth;
 03633            var reqMaxH = state.BaseRequest.MaxHeight;
 03634            var threeDFormat = state.MediaSource.Video3DFormat;
 3635
 03636            var isNvDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 03637            var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 03638            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03639            var isSwEncoder = !isNvencEncoder;
 03640            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 03641            var isCuInCuOut = isNvDecoder && isNvencEncoder;
 3642
 03643            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03644            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03645            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03646            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03647            var doCuTonemap = IsHwTonemapAvailable(state, options);
 3648
 03649            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03650            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03651            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 03652            var hasAssSubs = hasSubs
 03653                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 03654                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 03655            var subW = state.SubtitleStream?.Width;
 03656            var subH = state.SubtitleStream?.Height;
 3657
 03658            var rotation = state.VideoStream?.Rotation ?? 0;
 03659            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 03660            var doCuTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_cuda");
 03661            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isNvDecoder && doCuTranspose));
 03662            var swpInW = swapWAndH ? inH : inW;
 03663            var swpInH = swapWAndH ? inW : inH;
 3664
 3665            /* Make main filters for video stream */
 03666            var mainFilters = new List<string>();
 3667
 03668            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doCuTonemap));
 3669
 03670            if (isSwDecoder)
 3671            {
 3672                // INPUT sw surface(memory)
 3673                // sw deint
 03674                if (doDeintH2645)
 3675                {
 03676                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 03677                    mainFilters.Add(swDeintFilter);
 3678                }
 3679
 03680                var outFormat = doCuTonemap ? "yuv420p10le" : "yuv420p";
 03681                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 3682                // sw scale
 03683                mainFilters.Add(swScaleFilter);
 03684                mainFilters.Add($"format={outFormat}");
 3685
 3686                // sw => hw
 03687                if (doCuTonemap)
 3688                {
 03689                    mainFilters.Add("hwupload=derive_device=cuda");
 3690                }
 3691            }
 3692
 03693            if (isNvDecoder)
 3694            {
 3695                // INPUT cuda surface(vram)
 3696                // hw deint
 03697                if (doDeintH2645)
 3698                {
 03699                    var deintFilter = GetHwDeinterlaceFilter(state, options, "cuda");
 03700                    mainFilters.Add(deintFilter);
 3701                }
 3702
 3703                // hw transpose
 03704                if (doCuTranspose)
 3705                {
 03706                    mainFilters.Add($"transpose_cuda=dir={transposeDir}");
 3707                }
 3708
 03709                var isRext = IsVideoStreamHevcRext(state);
 03710                var outFormat = doCuTonemap ? (isRext ? "p010" : string.Empty) : "yuv420p";
 03711                var hwScaleFilter = GetHwScaleFilter("scale", "cuda", outFormat, false, swpInW, swpInH, reqW, reqH, reqM
 3712                // hw scale
 03713                mainFilters.Add(hwScaleFilter);
 3714            }
 3715
 3716            // hw tonemap
 03717            if (doCuTonemap)
 3718            {
 03719                var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p", isMjpegEncoder);
 03720                mainFilters.Add(tonemapFilter);
 3721            }
 3722
 03723            var memoryOutput = false;
 03724            var isUploadForCuTonemap = isSwDecoder && doCuTonemap;
 03725            if ((isNvDecoder && isSwEncoder) || (isUploadForCuTonemap && hasSubs))
 3726            {
 03727                memoryOutput = true;
 3728
 3729                // OUTPUT yuv420p surface(memory)
 03730                mainFilters.Add("hwdownload");
 03731                mainFilters.Add("format=yuv420p");
 3732            }
 3733
 3734            // OUTPUT yuv420p surface(memory)
 03735            if (isSwDecoder && isNvencEncoder && !isUploadForCuTonemap)
 3736            {
 03737                memoryOutput = true;
 3738            }
 3739
 03740            if (memoryOutput)
 3741            {
 3742                // text subtitles
 03743                if (hasTextSubs)
 3744                {
 03745                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03746                    mainFilters.Add(textSubtitlesFilter);
 3747                }
 3748            }
 3749
 3750            // OUTPUT cuda(yuv420p) surface(vram)
 3751
 3752            /* Make sub and overlay filters for subtitle stream */
 03753            var subFilters = new List<string>();
 03754            var overlayFilters = new List<string>();
 03755            if (isCuInCuOut)
 3756            {
 03757                if (hasSubs)
 3758                {
 03759                    if (hasGraphicalSubs)
 3760                    {
 03761                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 03762                        subFilters.Add(subPreProcFilters);
 03763                        subFilters.Add("format=yuva420p");
 3764                    }
 03765                    else if (hasTextSubs)
 3766                    {
 03767                        var framerate = state.VideoStream?.RealFrameRate;
 03768                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 3769
 3770                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 03771                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 03772                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 03773                        subFilters.Add(alphaSrcFilter);
 03774                        subFilters.Add("format=yuva420p");
 03775                        subFilters.Add(subTextSubtitlesFilter);
 3776                    }
 3777
 03778                    subFilters.Add("hwupload=derive_device=cuda");
 03779                    overlayFilters.Add("overlay_cuda=eof_action=pass:repeatlast=0");
 3780                }
 3781            }
 3782            else
 3783            {
 03784                if (hasGraphicalSubs)
 3785                {
 03786                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 03787                    subFilters.Add(subPreProcFilters);
 03788                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 3789                }
 3790            }
 3791
 03792            return (mainFilters, subFilters, overlayFilters);
 3793        }
 3794
 3795        /// <summary>
 3796        /// Gets the parameter of AMD AMF filter chain.
 3797        /// </summary>
 3798        /// <param name="state">Encoding state.</param>
 3799        /// <param name="options">Encoding options.</param>
 3800        /// <param name="vidEncoder">Video encoder to use.</param>
 3801        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3802        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVidFilterChain(
 3803            EncodingJobInfo state,
 3804            EncodingOptions options,
 3805            string vidEncoder)
 3806        {
 03807            if (options.HardwareAccelerationType != HardwareAccelerationType.amf)
 3808            {
 03809                return (null, null, null);
 3810            }
 3811
 03812            var isWindows = OperatingSystem.IsWindows();
 03813            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03814            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03815            var isSwEncoder = !vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 03816            var isAmfDx11OclSupported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va") && IsOpenclFullSupported()
 3817
 3818            // legacy d3d11va pipeline(copy-back)
 03819            if ((isSwDecoder && isSwEncoder)
 03820                || !isAmfDx11OclSupported
 03821                || !_mediaEncoder.SupportsFilter("alphasrc"))
 3822            {
 03823                return GetSwVidFilterChain(state, options, vidEncoder);
 3824            }
 3825
 3826            // preferred d3d11va + opencl filters + amf pipeline
 03827            return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 3828        }
 3829
 3830        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdDx11VidFiltersPref
 3831            EncodingJobInfo state,
 3832            EncodingOptions options,
 3833            string vidDecoder,
 3834            string vidEncoder)
 3835        {
 03836            var inW = state.VideoStream?.Width;
 03837            var inH = state.VideoStream?.Height;
 03838            var reqW = state.BaseRequest.Width;
 03839            var reqH = state.BaseRequest.Height;
 03840            var reqMaxW = state.BaseRequest.MaxWidth;
 03841            var reqMaxH = state.BaseRequest.MaxHeight;
 03842            var threeDFormat = state.MediaSource.Video3DFormat;
 3843
 03844            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 03845            var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 03846            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03847            var isSwEncoder = !isAmfEncoder;
 03848            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 03849            var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder;
 3850
 03851            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03852            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03853            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03854            var doOclTonemap = IsHwTonemapAvailable(state, options);
 3855
 03856            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03857            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03858            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 03859            var hasAssSubs = hasSubs
 03860                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 03861                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 03862            var subW = state.SubtitleStream?.Width;
 03863            var subH = state.SubtitleStream?.Height;
 3864
 03865            var rotation = state.VideoStream?.Rotation ?? 0;
 03866            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 03867            var doOclTranspose = !string.IsNullOrEmpty(transposeDir)
 03868                && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TransposeOpenclReversal);
 03869            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isD3d11vaDecoder && doOclTranspose));
 03870            var swpInW = swapWAndH ? inH : inW;
 03871            var swpInH = swapWAndH ? inW : inH;
 3872
 3873            /* Make main filters for video stream */
 03874            var mainFilters = new List<string>();
 3875
 03876            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 3877
 03878            if (isSwDecoder)
 3879            {
 3880                // INPUT sw surface(memory)
 3881                // sw deint
 03882                if (doDeintH2645)
 3883                {
 03884                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 03885                    mainFilters.Add(swDeintFilter);
 3886                }
 3887
 03888                var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
 03889                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 3890                // sw scale
 03891                mainFilters.Add(swScaleFilter);
 03892                mainFilters.Add($"format={outFormat}");
 3893
 3894                // keep video at memory except ocl tonemap,
 3895                // since the overhead caused by hwupload >>> using sw filter.
 3896                // sw => hw
 03897                if (doOclTonemap)
 3898                {
 03899                    mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=24");
 03900                    mainFilters.Add("format=d3d11");
 03901                    mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 3902                }
 3903            }
 3904
 03905            if (isD3d11vaDecoder)
 3906            {
 3907                // INPUT d3d11 surface(vram)
 3908                // map from d3d11va to opencl via d3d11-opencl interop.
 03909                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 3910
 3911                // hw deint <= TODO: finish the 'yadif_opencl' filter
 3912
 3913                // hw transpose
 03914                if (doOclTranspose)
 3915                {
 03916                    mainFilters.Add($"transpose_opencl=dir={transposeDir}");
 3917                }
 3918
 03919                var outFormat = doOclTonemap ? string.Empty : "nv12";
 03920                var hwScaleFilter = GetHwScaleFilter("scale", "opencl", outFormat, false, swpInW, swpInH, reqW, reqH, re
 3921                // hw scale
 03922                mainFilters.Add(hwScaleFilter);
 3923            }
 3924
 3925            // hw tonemap
 03926            if (doOclTonemap)
 3927            {
 03928                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 03929                mainFilters.Add(tonemapFilter);
 3930            }
 3931
 03932            var memoryOutput = false;
 03933            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 03934            if (isD3d11vaDecoder && isSwEncoder)
 3935            {
 03936                memoryOutput = true;
 3937
 3938                // OUTPUT nv12 surface(memory)
 3939                // prefer hwmap to hwdownload on opencl.
 03940                var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap=mode=read";
 03941                mainFilters.Add(hwTransferFilter);
 03942                mainFilters.Add("format=nv12");
 3943            }
 3944
 3945            // OUTPUT yuv420p surface
 03946            if (isSwDecoder && isAmfEncoder && !isUploadForOclTonemap)
 3947            {
 03948                memoryOutput = true;
 3949            }
 3950
 03951            if (memoryOutput)
 3952            {
 3953                // text subtitles
 03954                if (hasTextSubs)
 3955                {
 03956                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03957                    mainFilters.Add(textSubtitlesFilter);
 3958                }
 3959            }
 3960
 03961            if ((isDxInDxOut || isUploadForOclTonemap) && !hasSubs)
 3962            {
 3963                // OUTPUT d3d11(nv12) surface(vram)
 3964                // reverse-mapping via d3d11-opencl interop.
 03965                mainFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 03966                mainFilters.Add("format=d3d11");
 3967            }
 3968
 3969            /* Make sub and overlay filters for subtitle stream */
 03970            var subFilters = new List<string>();
 03971            var overlayFilters = new List<string>();
 03972            if (isDxInDxOut || isUploadForOclTonemap)
 3973            {
 03974                if (hasSubs)
 3975                {
 03976                    if (hasGraphicalSubs)
 3977                    {
 03978                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 03979                        subFilters.Add(subPreProcFilters);
 03980                        subFilters.Add("format=yuva420p");
 3981                    }
 03982                    else if (hasTextSubs)
 3983                    {
 03984                        var framerate = state.VideoStream?.RealFrameRate;
 03985                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 3986
 3987                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 03988                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 03989                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 03990                        subFilters.Add(alphaSrcFilter);
 03991                        subFilters.Add("format=yuva420p");
 03992                        subFilters.Add(subTextSubtitlesFilter);
 3993                    }
 3994
 03995                    subFilters.Add("hwupload=derive_device=opencl");
 03996                    overlayFilters.Add("overlay_opencl=eof_action=pass:repeatlast=0");
 03997                    overlayFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 03998                    overlayFilters.Add("format=d3d11");
 3999                }
 4000            }
 04001            else if (memoryOutput)
 4002            {
 04003                if (hasGraphicalSubs)
 4004                {
 04005                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04006                    subFilters.Add(subPreProcFilters);
 04007                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4008                }
 4009            }
 4010
 04011            return (mainFilters, subFilters, overlayFilters);
 4012        }
 4013
 4014        /// <summary>
 4015        /// Gets the parameter of Intel QSV filter chain.
 4016        /// </summary>
 4017        /// <param name="state">Encoding state.</param>
 4018        /// <param name="options">Encoding options.</param>
 4019        /// <param name="vidEncoder">Video encoder to use.</param>
 4020        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4021        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVidFilterChain(
 4022            EncodingJobInfo state,
 4023            EncodingOptions options,
 4024            string vidEncoder)
 4025        {
 04026            if (options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 4027            {
 04028                return (null, null, null);
 4029            }
 4030
 04031            var isWindows = OperatingSystem.IsWindows();
 04032            var isLinux = OperatingSystem.IsLinux();
 04033            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04034            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04035            var isSwEncoder = !vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04036            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 04037            var isIntelDx11OclSupported = isWindows
 04038                && _mediaEncoder.SupportsHwaccel("d3d11va")
 04039                && isQsvOclSupported;
 04040            var isIntelVaapiOclSupported = isLinux
 04041                && IsVaapiSupported(state)
 04042                && isQsvOclSupported;
 4043
 4044            // legacy qsv pipeline(copy-back)
 04045            if ((isSwDecoder && isSwEncoder)
 04046                || (!isIntelVaapiOclSupported && !isIntelDx11OclSupported)
 04047                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4048            {
 04049                return GetSwVidFilterChain(state, options, vidEncoder);
 4050            }
 4051
 4052            // preferred qsv(vaapi) + opencl filters pipeline
 04053            if (isIntelVaapiOclSupported)
 4054            {
 04055                return GetIntelQsvVaapiVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4056            }
 4057
 4058            // preferred qsv(d3d11) + opencl filters pipeline
 04059            if (isIntelDx11OclSupported)
 4060            {
 04061                return GetIntelQsvDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4062            }
 4063
 04064            return (null, null, null);
 4065        }
 4066
 4067        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvDx11VidFilter
 4068            EncodingJobInfo state,
 4069            EncodingOptions options,
 4070            string vidDecoder,
 4071            string vidEncoder)
 4072        {
 04073            var inW = state.VideoStream?.Width;
 04074            var inH = state.VideoStream?.Height;
 04075            var reqW = state.BaseRequest.Width;
 04076            var reqH = state.BaseRequest.Height;
 04077            var reqMaxW = state.BaseRequest.MaxWidth;
 04078            var reqMaxH = state.BaseRequest.MaxHeight;
 04079            var threeDFormat = state.MediaSource.Video3DFormat;
 4080
 04081            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04082            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04083            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04084            var isHwDecoder = isD3d11vaDecoder || isQsvDecoder;
 04085            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04086            var isSwEncoder = !isQsvEncoder;
 04087            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04088            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4089
 04090            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04091            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04092            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04093            var doVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04094            var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options);
 04095            var doTonemap = doVppTonemap || doOclTonemap;
 4096
 04097            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04098            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04099            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04100            var hasAssSubs = hasSubs
 04101                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04102                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04103            var subW = state.SubtitleStream?.Width;
 04104            var subH = state.SubtitleStream?.Height;
 4105
 04106            var rotation = state.VideoStream?.Rotation ?? 0;
 04107            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04108            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04109            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isD3d11vaDecoder || isQsvDecoder) && doVppTran
 04110            var swpInW = swapWAndH ? inH : inW;
 04111            var swpInH = swapWAndH ? inW : inH;
 4112
 4113            /* Make main filters for video stream */
 04114            var mainFilters = new List<string>();
 4115
 04116            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4117
 04118            if (isSwDecoder)
 4119            {
 4120                // INPUT sw surface(memory)
 4121                // sw deint
 04122                if (doDeintH2645)
 4123                {
 04124                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04125                    mainFilters.Add(swDeintFilter);
 4126                }
 4127
 04128                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04129                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04130                if (isMjpegEncoder && !doOclTonemap)
 4131                {
 4132                    // sw decoder + hw mjpeg encoder
 04133                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4134                }
 4135
 4136                // sw scale
 04137                mainFilters.Add(swScaleFilter);
 04138                mainFilters.Add($"format={outFormat}");
 4139
 4140                // keep video at memory except ocl tonemap,
 4141                // since the overhead caused by hwupload >>> using sw filter.
 4142                // sw => hw
 04143                if (doOclTonemap)
 4144                {
 04145                    mainFilters.Add("hwupload=derive_device=opencl");
 4146                }
 4147            }
 04148            else if (isD3d11vaDecoder || isQsvDecoder)
 4149            {
 04150                var isRext = IsVideoStreamHevcRext(state);
 04151                var twoPassVppTonemap = false;
 04152                var doVppFullRangeOut = isMjpegEncoder
 04153                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04154                var doVppScaleModeHq = isMjpegEncoder
 04155                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 04156                var doVppProcamp = false;
 04157                var procampParams = string.Empty;
 04158                var procampParamsString = string.Empty;
 04159                if (doVppTonemap)
 4160                {
 04161                    if (isRext)
 4162                    {
 4163                        // VPP tonemap requires p010 input
 04164                        twoPassVppTonemap = true;
 4165                    }
 4166
 04167                    if (options.VppTonemappingBrightness != 0
 04168                        && options.VppTonemappingBrightness >= -100
 04169                        && options.VppTonemappingBrightness <= 100)
 4170                    {
 04171                        procampParamsString += ":brightness={0}";
 04172                        twoPassVppTonemap = doVppProcamp = true;
 4173                    }
 4174
 04175                    if (options.VppTonemappingContrast > 1
 04176                        && options.VppTonemappingContrast <= 10)
 4177                    {
 04178                        procampParamsString += ":contrast={1}";
 04179                        twoPassVppTonemap = doVppProcamp = true;
 4180                    }
 4181
 04182                    if (doVppProcamp)
 4183                    {
 04184                        procampParamsString += ":procamp=1:async_depth=2";
 04185                        procampParams = string.Format(
 04186                            CultureInfo.InvariantCulture,
 04187                            procampParamsString,
 04188                            options.VppTonemappingBrightness,
 04189                            options.VppTonemappingContrast);
 4190                    }
 4191                }
 4192
 04193                var outFormat = doOclTonemap ? ((doVppTranspose || isRext) ? "p010" : string.Empty) : "nv12";
 04194                outFormat = twoPassVppTonemap ? "p010" : outFormat;
 4195
 04196                var swapOutputWandH = doVppTranspose && swapWAndH;
 04197                var hwScaleFilter = GetHwScaleFilter("vpp", "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, req
 4198
 04199                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose)
 4200                {
 04201                    hwScaleFilter += $":transpose={transposeDir}";
 4202                }
 4203
 04204                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4205                {
 04206                    hwScaleFilter += (doVppFullRangeOut && !doOclTonemap) ? ":out_range=pc" : string.Empty;
 04207                    hwScaleFilter += doVppScaleModeHq ? ":scale_mode=hq" : string.Empty;
 4208                }
 4209
 04210                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTonemap)
 4211                {
 04212                    hwScaleFilter += doVppProcamp ? procampParams : (twoPassVppTonemap ? string.Empty : ":tonemap=1");
 4213                }
 4214
 04215                if (isD3d11vaDecoder)
 4216                {
 04217                    if (!string.IsNullOrEmpty(hwScaleFilter) || doDeintH2645)
 4218                    {
 4219                        // INPUT d3d11 surface(vram)
 4220                        // map from d3d11va to qsv.
 04221                        mainFilters.Add("hwmap=derive_device=qsv");
 4222                    }
 4223                }
 4224
 4225                // hw deint
 04226                if (doDeintH2645)
 4227                {
 04228                    var deintFilter = GetHwDeinterlaceFilter(state, options, "qsv");
 04229                    mainFilters.Add(deintFilter);
 4230                }
 4231
 4232                // hw transpose & scale & tonemap(w/o procamp)
 04233                mainFilters.Add(hwScaleFilter);
 4234
 4235                // hw tonemap(w/ procamp)
 04236                if (doVppTonemap && twoPassVppTonemap)
 4237                {
 04238                    mainFilters.Add("vpp_qsv=tonemap=1:format=nv12:async_depth=2");
 4239                }
 4240
 4241                // force bt709 just in case vpp tonemap is not triggered or using MSDK instead of VPL.
 04242                if (doVppTonemap)
 4243                {
 04244                    mainFilters.Add(GetOverwriteColorPropertiesParam(state, false));
 4245                }
 4246            }
 4247
 04248            if (doOclTonemap && isHwDecoder)
 4249            {
 4250                // map from qsv to opencl via qsv(d3d11)-opencl interop.
 04251                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4252            }
 4253
 4254            // hw tonemap
 04255            if (doOclTonemap)
 4256            {
 04257                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04258                mainFilters.Add(tonemapFilter);
 4259            }
 4260
 04261            var memoryOutput = false;
 04262            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04263            var isHwmapUsable = isSwEncoder && doOclTonemap;
 04264            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4265            {
 04266                memoryOutput = true;
 4267
 4268                // OUTPUT nv12 surface(memory)
 4269                // prefer hwmap to hwdownload on opencl.
 4270                // qsv hwmap is not fully implemented for the time being.
 04271                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04272                mainFilters.Add("format=nv12");
 4273            }
 4274
 4275            // OUTPUT nv12 surface(memory)
 04276            if (isSwDecoder && isQsvEncoder)
 4277            {
 04278                memoryOutput = true;
 4279            }
 4280
 04281            if (memoryOutput)
 4282            {
 4283                // text subtitles
 04284                if (hasTextSubs)
 4285                {
 04286                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04287                    mainFilters.Add(textSubtitlesFilter);
 4288                }
 4289            }
 4290
 04291            if (isQsvInQsvOut && doOclTonemap)
 4292            {
 4293                // OUTPUT qsv(nv12) surface(vram)
 4294                // reverse-mapping via qsv(d3d11)-opencl interop.
 04295                mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1");
 04296                mainFilters.Add("format=qsv");
 4297            }
 4298
 4299            /* Make sub and overlay filters for subtitle stream */
 04300            var subFilters = new List<string>();
 04301            var overlayFilters = new List<string>();
 04302            if (isQsvInQsvOut)
 4303            {
 04304                if (hasSubs)
 4305                {
 04306                    if (hasGraphicalSubs)
 4307                    {
 4308                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04309                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04310                        subFilters.Add(subPreProcFilters);
 04311                        subFilters.Add("format=bgra");
 4312                    }
 04313                    else if (hasTextSubs)
 4314                    {
 04315                        var framerate = state.VideoStream?.RealFrameRate;
 04316                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4317
 4318                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 04319                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04320                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04321                        subFilters.Add(alphaSrcFilter);
 04322                        subFilters.Add("format=bgra");
 04323                        subFilters.Add(subTextSubtitlesFilter);
 4324                    }
 4325
 4326                    // qsv requires a fixed pool size.
 4327                    // default to 64 otherwise it will fail on certain iGPU.
 04328                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4329
 04330                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04331                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04332                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04333                        : string.Empty;
 04334                    var overlayQsvFilter = string.Format(
 04335                        CultureInfo.InvariantCulture,
 04336                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04337                        overlaySize);
 04338                    overlayFilters.Add(overlayQsvFilter);
 4339                }
 4340            }
 04341            else if (memoryOutput)
 4342            {
 04343                if (hasGraphicalSubs)
 4344                {
 04345                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04346                    subFilters.Add(subPreProcFilters);
 04347                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4348                }
 4349            }
 4350
 04351            return (mainFilters, subFilters, overlayFilters);
 4352        }
 4353
 4354        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvVaapiVidFilte
 4355            EncodingJobInfo state,
 4356            EncodingOptions options,
 4357            string vidDecoder,
 4358            string vidEncoder)
 4359        {
 04360            var inW = state.VideoStream?.Width;
 04361            var inH = state.VideoStream?.Height;
 04362            var reqW = state.BaseRequest.Width;
 04363            var reqH = state.BaseRequest.Height;
 04364            var reqMaxW = state.BaseRequest.MaxWidth;
 04365            var reqMaxH = state.BaseRequest.MaxHeight;
 04366            var threeDFormat = state.MediaSource.Video3DFormat;
 4367
 04368            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04369            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04370            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04371            var isHwDecoder = isVaapiDecoder || isQsvDecoder;
 04372            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04373            var isSwEncoder = !isQsvEncoder;
 04374            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04375            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4376
 04377            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04378            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04379            var doVaVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04380            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04381            var doTonemap = doVaVppTonemap || doOclTonemap;
 04382            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4383
 04384            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04385            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04386            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04387            var hasAssSubs = hasSubs
 04388                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04389                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04390            var subW = state.SubtitleStream?.Width;
 04391            var subH = state.SubtitleStream?.Height;
 4392
 04393            var rotation = state.VideoStream?.Rotation ?? 0;
 04394            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04395            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04396            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isVaapiDecoder || isQsvDecoder) && doVppTransp
 04397            var swpInW = swapWAndH ? inH : inW;
 04398            var swpInH = swapWAndH ? inW : inH;
 4399
 4400            /* Make main filters for video stream */
 04401            var mainFilters = new List<string>();
 4402
 04403            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4404
 04405            if (isSwDecoder)
 4406            {
 4407                // INPUT sw surface(memory)
 4408                // sw deint
 04409                if (doDeintH2645)
 4410                {
 04411                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04412                    mainFilters.Add(swDeintFilter);
 4413                }
 4414
 04415                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04416                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04417                if (isMjpegEncoder && !doOclTonemap)
 4418                {
 4419                    // sw decoder + hw mjpeg encoder
 04420                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4421                }
 4422
 4423                // sw scale
 04424                mainFilters.Add(swScaleFilter);
 04425                mainFilters.Add($"format={outFormat}");
 4426
 4427                // keep video at memory except ocl tonemap,
 4428                // since the overhead caused by hwupload >>> using sw filter.
 4429                // sw => hw
 04430                if (doOclTonemap)
 4431                {
 04432                    mainFilters.Add("hwupload=derive_device=opencl");
 4433                }
 4434            }
 04435            else if (isVaapiDecoder || isQsvDecoder)
 4436            {
 04437                var hwFilterSuffix = isVaapiDecoder ? "vaapi" : "qsv";
 04438                var isRext = IsVideoStreamHevcRext(state);
 04439                var doVppFullRangeOut = isMjpegEncoder
 04440                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04441                var doVppScaleModeHq = isMjpegEncoder
 04442                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 4443
 4444                // INPUT vaapi/qsv surface(vram)
 4445                // hw deint
 04446                if (doDeintH2645)
 4447                {
 04448                    var deintFilter = GetHwDeinterlaceFilter(state, options, hwFilterSuffix);
 04449                    mainFilters.Add(deintFilter);
 4450                }
 4451
 4452                // hw transpose(vaapi vpp)
 04453                if (isVaapiDecoder && doVppTranspose)
 4454                {
 04455                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 4456                }
 4457
 04458                var outFormat = doTonemap ? (((isQsvDecoder && doVppTranspose) || isRext) ? "p010" : string.Empty) : "nv
 04459                var swapOutputWandH = isQsvDecoder && doVppTranspose && swapWAndH;
 04460                var hwScalePrefix = isQsvDecoder ? "vpp" : "scale";
 04461                var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, hwFilterSuffix, outFormat, swapOutputWandH, swpInW, 
 4462
 04463                if (!string.IsNullOrEmpty(hwScaleFilter) && isQsvDecoder && doVppTranspose)
 4464                {
 04465                    hwScaleFilter += $":transpose={transposeDir}";
 4466                }
 4467
 04468                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4469                {
 04470                    hwScaleFilter += ((isQsvDecoder && !doVppFullRangeOut) || doOclTonemap) ? string.Empty : ":out_range
 04471                    hwScaleFilter += isQsvDecoder ? (doVppScaleModeHq ? ":scale_mode=hq" : string.Empty) : ":mode=hq";
 4472                }
 4473
 4474                // allocate extra pool sizes for vaapi vpp scale
 04475                if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder)
 4476                {
 04477                    hwScaleFilter += ":extra_hw_frames=24";
 4478                }
 4479
 4480                // hw transpose(qsv vpp) & scale
 04481                mainFilters.Add(hwScaleFilter);
 4482            }
 4483
 4484            // vaapi vpp tonemap
 04485            if (doVaVppTonemap && isHwDecoder)
 4486            {
 04487                if (isQsvDecoder)
 4488                {
 4489                    // map from qsv to vaapi.
 04490                    mainFilters.Add("hwmap=derive_device=vaapi");
 04491                    mainFilters.Add("format=vaapi");
 4492                }
 4493
 04494                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 04495                mainFilters.Add(tonemapFilter);
 4496
 04497                if (isQsvDecoder)
 4498                {
 4499                    // map from vaapi to qsv.
 04500                    mainFilters.Add("hwmap=derive_device=qsv");
 04501                    mainFilters.Add("format=qsv");
 4502                }
 4503            }
 4504
 04505            if (doOclTonemap && isHwDecoder)
 4506            {
 4507                // map from qsv to opencl via qsv(vaapi)-opencl interop.
 04508                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4509            }
 4510
 4511            // ocl tonemap
 04512            if (doOclTonemap)
 4513            {
 04514                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04515                mainFilters.Add(tonemapFilter);
 4516            }
 4517
 04518            var memoryOutput = false;
 04519            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04520            var isHwmapUsable = isSwEncoder && (doOclTonemap || isVaapiDecoder);
 04521            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4522            {
 04523                memoryOutput = true;
 4524
 4525                // OUTPUT nv12 surface(memory)
 4526                // prefer hwmap to hwdownload on opencl/vaapi.
 4527                // qsv hwmap is not fully implemented for the time being.
 04528                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04529                mainFilters.Add("format=nv12");
 4530            }
 4531
 4532            // OUTPUT nv12 surface(memory)
 04533            if (isSwDecoder && isQsvEncoder)
 4534            {
 04535                memoryOutput = true;
 4536            }
 4537
 04538            if (memoryOutput)
 4539            {
 4540                // text subtitles
 04541                if (hasTextSubs)
 4542                {
 04543                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04544                    mainFilters.Add(textSubtitlesFilter);
 4545                }
 4546            }
 4547
 04548            if (isQsvInQsvOut)
 4549            {
 04550                if (doOclTonemap)
 4551                {
 4552                    // OUTPUT qsv(nv12) surface(vram)
 4553                    // reverse-mapping via qsv(vaapi)-opencl interop.
 4554                    // add extra pool size to avoid the 'cannot allocate memory' error on hevc_qsv.
 04555                    mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1:extra_hw_frames=16");
 04556                    mainFilters.Add("format=qsv");
 4557                }
 04558                else if (isVaapiDecoder)
 4559                {
 04560                    mainFilters.Add("hwmap=derive_device=qsv");
 04561                    mainFilters.Add("format=qsv");
 4562                }
 4563            }
 4564
 4565            /* Make sub and overlay filters for subtitle stream */
 04566            var subFilters = new List<string>();
 04567            var overlayFilters = new List<string>();
 04568            if (isQsvInQsvOut)
 4569            {
 04570                if (hasSubs)
 4571                {
 04572                    if (hasGraphicalSubs)
 4573                    {
 4574                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04575                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04576                        subFilters.Add(subPreProcFilters);
 04577                        subFilters.Add("format=bgra");
 4578                    }
 04579                    else if (hasTextSubs)
 4580                    {
 04581                        var framerate = state.VideoStream?.RealFrameRate;
 04582                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4583
 04584                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04585                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04586                        subFilters.Add(alphaSrcFilter);
 04587                        subFilters.Add("format=bgra");
 04588                        subFilters.Add(subTextSubtitlesFilter);
 4589                    }
 4590
 4591                    // qsv requires a fixed pool size.
 4592                    // default to 64 otherwise it will fail on certain iGPU.
 04593                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4594
 04595                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04596                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04597                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04598                        : string.Empty;
 04599                    var overlayQsvFilter = string.Format(
 04600                        CultureInfo.InvariantCulture,
 04601                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04602                        overlaySize);
 04603                    overlayFilters.Add(overlayQsvFilter);
 4604                }
 4605            }
 04606            else if (memoryOutput)
 4607            {
 04608                if (hasGraphicalSubs)
 4609                {
 04610                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04611                    subFilters.Add(subPreProcFilters);
 04612                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4613                }
 4614            }
 4615
 04616            return (mainFilters, subFilters, overlayFilters);
 4617        }
 4618
 4619        /// <summary>
 4620        /// Gets the parameter of Intel/AMD VAAPI filter chain.
 4621        /// </summary>
 4622        /// <param name="state">Encoding state.</param>
 4623        /// <param name="options">Encoding options.</param>
 4624        /// <param name="vidEncoder">Video encoder to use.</param>
 4625        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4626        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiVidFilterChain(
 4627            EncodingJobInfo state,
 4628            EncodingOptions options,
 4629            string vidEncoder)
 4630        {
 04631            if (options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 4632            {
 04633                return (null, null, null);
 4634            }
 4635
 04636            var isLinux = OperatingSystem.IsLinux();
 04637            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04638            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04639            var isSwEncoder = !vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04640            var isVaapiFullSupported = isLinux && IsVaapiSupported(state) && IsVaapiFullSupported();
 04641            var isVaapiOclSupported = isVaapiFullSupported && IsOpenclFullSupported();
 04642            var isVaapiVkSupported = isVaapiFullSupported && IsVulkanFullSupported();
 4643
 4644            // legacy vaapi pipeline(copy-back)
 04645            if ((isSwDecoder && isSwEncoder)
 04646                || !isVaapiOclSupported
 04647                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4648            {
 04649                var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
 4650
 04651                if (!isSwEncoder)
 4652                {
 04653                    var newfilters = new List<string>();
 04654                    var noOverlay = swFilterChain.OverlayFilters.Count == 0;
 04655                    newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
 04656                    newfilters.Add("hwupload=derive_device=vaapi");
 4657
 04658                    var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
 04659                    var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
 04660                    return (mainFilters, swFilterChain.SubFilters, overlayFilters);
 4661                }
 4662
 04663                return swFilterChain;
 4664            }
 4665
 4666            // preferred vaapi + opencl filters pipeline
 04667            if (_mediaEncoder.IsVaapiDeviceInteliHD)
 4668            {
 4669                // Intel iHD path, with extra vpp tonemap and overlay support.
 04670                return GetIntelVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4671            }
 4672
 4673            // preferred vaapi + vulkan filters pipeline
 04674            if (_mediaEncoder.IsVaapiDeviceAmd
 04675                && isVaapiVkSupported
 04676                && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 04677                && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 4678            {
 4679                // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support.
 04680                return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4681            }
 4682
 4683            // Intel i965 and Amd legacy driver path, only featuring scale and deinterlace support.
 04684            return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4685        }
 4686
 4687        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVaapiFullVidFilt
 4688            EncodingJobInfo state,
 4689            EncodingOptions options,
 4690            string vidDecoder,
 4691            string vidEncoder)
 4692        {
 04693            var inW = state.VideoStream?.Width;
 04694            var inH = state.VideoStream?.Height;
 04695            var reqW = state.BaseRequest.Width;
 04696            var reqH = state.BaseRequest.Height;
 04697            var reqMaxW = state.BaseRequest.MaxWidth;
 04698            var reqMaxH = state.BaseRequest.MaxHeight;
 04699            var threeDFormat = state.MediaSource.Video3DFormat;
 4700
 04701            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04702            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04703            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04704            var isSwEncoder = !isVaapiEncoder;
 04705            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04706            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 4707
 04708            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04709            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04710            var doVaVppTonemap = isVaapiDecoder && IsIntelVppTonemapAvailable(state, options);
 04711            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04712            var doTonemap = doVaVppTonemap || doOclTonemap;
 04713            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4714
 04715            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04716            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04717            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04718            var hasAssSubs = hasSubs
 04719                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04720                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04721            var subW = state.SubtitleStream?.Width;
 04722            var subH = state.SubtitleStream?.Height;
 4723
 04724            var rotation = state.VideoStream?.Rotation ?? 0;
 04725            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04726            var doVaVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04727            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVaVppTranspose));
 04728            var swpInW = swapWAndH ? inH : inW;
 04729            var swpInH = swapWAndH ? inW : inH;
 4730
 4731            /* Make main filters for video stream */
 04732            var mainFilters = new List<string>();
 4733
 04734            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4735
 04736            if (isSwDecoder)
 4737            {
 4738                // INPUT sw surface(memory)
 4739                // sw deint
 04740                if (doDeintH2645)
 4741                {
 04742                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04743                    mainFilters.Add(swDeintFilter);
 4744                }
 4745
 04746                var outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 04747                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04748                if (isMjpegEncoder && !doOclTonemap)
 4749                {
 4750                    // sw decoder + hw mjpeg encoder
 04751                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4752                }
 4753
 4754                // sw scale
 04755                mainFilters.Add(swScaleFilter);
 04756                mainFilters.Add($"format={outFormat}");
 4757
 4758                // keep video at memory except ocl tonemap,
 4759                // since the overhead caused by hwupload >>> using sw filter.
 4760                // sw => hw
 04761                if (doOclTonemap)
 4762                {
 04763                    mainFilters.Add("hwupload=derive_device=opencl");
 4764                }
 4765            }
 04766            else if (isVaapiDecoder)
 4767            {
 04768                var isRext = IsVideoStreamHevcRext(state);
 4769
 4770                // INPUT vaapi surface(vram)
 4771                // hw deint
 04772                if (doDeintH2645)
 4773                {
 04774                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 04775                    mainFilters.Add(deintFilter);
 4776                }
 4777
 4778                // hw transpose
 04779                if (doVaVppTranspose)
 4780                {
 04781                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 4782                }
 4783
 04784                var outFormat = doTonemap ? (isRext ? "p010" : string.Empty) : "nv12";
 04785                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, swpInW, swpInH, reqW, reqH, req
 4786
 04787                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4788                {
 04789                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 04790                    hwScaleFilter += ":mode=hq";
 4791                }
 4792
 4793                // allocate extra pool sizes for vaapi vpp
 04794                if (!string.IsNullOrEmpty(hwScaleFilter))
 4795                {
 04796                    hwScaleFilter += ":extra_hw_frames=24";
 4797                }
 4798
 4799                // hw scale
 04800                mainFilters.Add(hwScaleFilter);
 4801            }
 4802
 4803            // vaapi vpp tonemap
 04804            if (doVaVppTonemap && isVaapiDecoder)
 4805            {
 04806                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 04807                mainFilters.Add(tonemapFilter);
 4808            }
 4809
 04810            if (doOclTonemap && isVaapiDecoder)
 4811            {
 4812                // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 04813                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4814            }
 4815
 4816            // ocl tonemap
 04817            if (doOclTonemap)
 4818            {
 04819                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04820                mainFilters.Add(tonemapFilter);
 4821            }
 4822
 04823            if (doOclTonemap && isVaInVaOut)
 4824            {
 4825                // OUTPUT vaapi(nv12) surface(vram)
 4826                // reverse-mapping via vaapi-opencl interop.
 04827                mainFilters.Add("hwmap=derive_device=vaapi:mode=write:reverse=1");
 04828                mainFilters.Add("format=vaapi");
 4829            }
 4830
 04831            var memoryOutput = false;
 04832            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04833            var isHwmapNotUsable = isUploadForOclTonemap && isVaapiEncoder;
 04834            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap)
 4835            {
 04836                memoryOutput = true;
 4837
 4838                // OUTPUT nv12 surface(memory)
 4839                // prefer hwmap to hwdownload on opencl/vaapi.
 04840                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap=mode=read");
 04841                mainFilters.Add("format=nv12");
 4842            }
 4843
 4844            // OUTPUT nv12 surface(memory)
 04845            if (isSwDecoder && isVaapiEncoder)
 4846            {
 04847                memoryOutput = true;
 4848            }
 4849
 04850            if (memoryOutput)
 4851            {
 4852                // text subtitles
 04853                if (hasTextSubs)
 4854                {
 04855                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04856                    mainFilters.Add(textSubtitlesFilter);
 4857                }
 4858            }
 4859
 04860            if (memoryOutput && isVaapiEncoder)
 4861            {
 04862                if (!hasGraphicalSubs)
 4863                {
 04864                    mainFilters.Add("hwupload_vaapi");
 4865                }
 4866            }
 4867
 4868            /* Make sub and overlay filters for subtitle stream */
 04869            var subFilters = new List<string>();
 04870            var overlayFilters = new List<string>();
 04871            if (isVaInVaOut)
 4872            {
 04873                if (hasSubs)
 4874                {
 04875                    if (hasGraphicalSubs)
 4876                    {
 4877                        // overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04878                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04879                        subFilters.Add(subPreProcFilters);
 04880                        subFilters.Add("format=bgra");
 4881                    }
 04882                    else if (hasTextSubs)
 4883                    {
 04884                        var framerate = state.VideoStream?.RealFrameRate;
 04885                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4886
 04887                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04888                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04889                        subFilters.Add(alphaSrcFilter);
 04890                        subFilters.Add("format=bgra");
 04891                        subFilters.Add(subTextSubtitlesFilter);
 4892                    }
 4893
 04894                    subFilters.Add("hwupload=derive_device=vaapi");
 4895
 04896                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04897                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04898                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04899                        : string.Empty;
 04900                    var overlayVaapiFilter = string.Format(
 04901                        CultureInfo.InvariantCulture,
 04902                        "overlay_vaapi=eof_action=pass:repeatlast=0{0}",
 04903                        overlaySize);
 04904                    overlayFilters.Add(overlayVaapiFilter);
 4905                }
 4906            }
 04907            else if (memoryOutput)
 4908            {
 04909                if (hasGraphicalSubs)
 4910                {
 04911                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04912                    subFilters.Add(subPreProcFilters);
 04913                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4914
 04915                    if (isVaapiEncoder)
 4916                    {
 04917                        overlayFilters.Add("hwupload_vaapi");
 4918                    }
 4919                }
 4920            }
 4921
 04922            return (mainFilters, subFilters, overlayFilters);
 4923        }
 4924
 4925        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVaapiFullVidFilter
 4926            EncodingJobInfo state,
 4927            EncodingOptions options,
 4928            string vidDecoder,
 4929            string vidEncoder)
 4930        {
 04931            var inW = state.VideoStream?.Width;
 04932            var inH = state.VideoStream?.Height;
 04933            var reqW = state.BaseRequest.Width;
 04934            var reqH = state.BaseRequest.Height;
 04935            var reqMaxW = state.BaseRequest.MaxWidth;
 04936            var reqMaxH = state.BaseRequest.MaxHeight;
 04937            var threeDFormat = state.MediaSource.Video3DFormat;
 4938
 04939            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04940            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04941            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04942            var isSwEncoder = !isVaapiEncoder;
 04943            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 4944
 04945            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04946            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04947            var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
 04948            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4949
 04950            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04951            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04952            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04953            var hasAssSubs = hasSubs
 04954                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04955                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 4956
 04957            var rotation = state.VideoStream?.Rotation ?? 0;
 04958            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04959            var doVkTranspose = isVaapiDecoder && !string.IsNullOrEmpty(transposeDir);
 04960            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVkTranspose));
 04961            var swpInW = swapWAndH ? inH : inW;
 04962            var swpInH = swapWAndH ? inW : inH;
 4963
 4964            /* Make main filters for video stream */
 04965            var mainFilters = new List<string>();
 4966
 04967            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doVkTonemap));
 4968
 04969            if (isSwDecoder)
 4970            {
 4971                // INPUT sw surface(memory)
 4972                // sw deint
 04973                if (doDeintH2645)
 4974                {
 04975                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04976                    mainFilters.Add(swDeintFilter);
 4977                }
 4978
 04979                if (doVkTonemap || hasSubs)
 4980                {
 4981                    // sw => hw
 04982                    mainFilters.Add("hwupload=derive_device=vulkan");
 04983                    mainFilters.Add("format=vulkan");
 4984                }
 4985                else
 4986                {
 4987                    // sw scale
 04988                    var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW,
 04989                    mainFilters.Add(swScaleFilter);
 04990                    mainFilters.Add("format=nv12");
 4991                }
 4992            }
 04993            else if (isVaapiDecoder)
 4994            {
 4995                // INPUT vaapi surface(vram)
 04996                if (doVkTranspose || doVkTonemap || hasSubs)
 4997                {
 4998                    // map from vaapi to vulkan/drm via interop (Polaris/gfx8+).
 04999                    if (_mediaEncoder.EncoderVersion >= _minFFmpegAlteredVaVkInterop)
 5000                    {
 05001                        if (doVkTranspose || !_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5002                        {
 5003                            // disable the indirect va-drm-vk mapping since it's no longer reliable.
 05004                            mainFilters.Add("hwmap=derive_device=drm");
 05005                            mainFilters.Add("format=drm_prime");
 05006                            mainFilters.Add("hwmap=derive_device=vulkan");
 05007                            mainFilters.Add("format=vulkan");
 5008
 5009                            // workaround for libplacebo using the imported vulkan frame on gfx8.
 05010                            if (!_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5011                            {
 05012                                mainFilters.Add("scale_vulkan");
 5013                            }
 5014                        }
 05015                        else if (doVkTonemap || hasSubs)
 5016                        {
 5017                            // non ad-hoc libplacebo also accepts drm_prime direct input.
 05018                            mainFilters.Add("hwmap=derive_device=drm");
 05019                            mainFilters.Add("format=drm_prime");
 5020                        }
 5021                    }
 5022                    else // legacy va-vk mapping that works only in jellyfin-ffmpeg6
 5023                    {
 05024                        mainFilters.Add("hwmap=derive_device=vulkan");
 05025                        mainFilters.Add("format=vulkan");
 5026                    }
 5027                }
 5028                else
 5029                {
 5030                    // hw deint
 05031                    if (doDeintH2645)
 5032                    {
 05033                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05034                        mainFilters.Add(deintFilter);
 5035                    }
 5036
 5037                    // hw scale
 05038                    var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", "nv12", false, inW, inH, reqW, reqH, reqMaxW,
 5039
 05040                    if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder && !doVkTonemap)
 5041                    {
 05042                        hwScaleFilter += ":out_range=pc:mode=hq";
 5043                    }
 5044
 05045                    mainFilters.Add(hwScaleFilter);
 5046                }
 5047            }
 5048
 5049            // vk transpose
 05050            if (doVkTranspose)
 5051            {
 05052                if (string.Equals(transposeDir, "reversal", StringComparison.OrdinalIgnoreCase))
 5053                {
 05054                    mainFilters.Add("flip_vulkan");
 5055                }
 5056                else
 5057                {
 05058                    mainFilters.Add($"transpose_vulkan=dir={transposeDir}");
 5059                }
 5060            }
 5061
 5062            // vk libplacebo
 05063            if (doVkTonemap || hasSubs)
 5064            {
 05065                var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, req
 05066                mainFilters.Add(libplaceboFilter);
 05067                mainFilters.Add("format=vulkan");
 5068            }
 5069
 05070            if (doVkTonemap && !hasSubs)
 5071            {
 5072                // OUTPUT vaapi(nv12) surface(vram)
 5073                // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05074                mainFilters.Add("hwmap=derive_device=vaapi");
 05075                mainFilters.Add("format=vaapi");
 5076
 5077                // clear the surf->meta_offset and output nv12
 05078                mainFilters.Add("scale_vaapi=format=nv12");
 5079
 5080                // hw deint
 05081                if (doDeintH2645)
 5082                {
 05083                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05084                    mainFilters.Add(deintFilter);
 5085                }
 5086            }
 5087
 05088            if (!hasSubs)
 5089            {
 5090                // OUTPUT nv12 surface(memory)
 05091                if (isSwEncoder && (doVkTonemap || isVaapiDecoder))
 5092                {
 05093                    mainFilters.Add("hwdownload");
 05094                    mainFilters.Add("format=nv12");
 5095                }
 5096
 05097                if (isSwDecoder && isVaapiEncoder && !doVkTonemap)
 5098                {
 05099                    mainFilters.Add("hwupload_vaapi");
 5100                }
 5101            }
 5102
 5103            /* Make sub and overlay filters for subtitle stream */
 05104            var subFilters = new List<string>();
 05105            var overlayFilters = new List<string>();
 05106            if (hasSubs)
 5107            {
 05108                if (hasGraphicalSubs)
 5109                {
 05110                    var subW = state.SubtitleStream?.Width;
 05111                    var subH = state.SubtitleStream?.Height;
 05112                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05113                    subFilters.Add(subPreProcFilters);
 05114                    subFilters.Add("format=bgra");
 5115                }
 05116                else if (hasTextSubs)
 5117                {
 05118                    var framerate = state.VideoStream?.RealFrameRate;
 05119                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5120
 05121                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05122                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05123                    subFilters.Add(alphaSrcFilter);
 05124                    subFilters.Add("format=bgra");
 05125                    subFilters.Add(subTextSubtitlesFilter);
 5126                }
 5127
 05128                subFilters.Add("hwupload=derive_device=vulkan");
 05129                subFilters.Add("format=vulkan");
 5130
 05131                overlayFilters.Add("overlay_vulkan=eof_action=pass:repeatlast=0");
 5132
 05133                if (isSwEncoder)
 5134                {
 5135                    // OUTPUT nv12 surface(memory)
 05136                    overlayFilters.Add("scale_vulkan=format=nv12");
 05137                    overlayFilters.Add("hwdownload");
 05138                    overlayFilters.Add("format=nv12");
 5139                }
 05140                else if (isVaapiEncoder)
 5141                {
 5142                    // OUTPUT vaapi(nv12) surface(vram)
 5143                    // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05144                    overlayFilters.Add("hwmap=derive_device=vaapi");
 05145                    overlayFilters.Add("format=vaapi");
 5146
 5147                    // clear the surf->meta_offset and output nv12
 05148                    overlayFilters.Add("scale_vaapi=format=nv12");
 5149
 5150                    // hw deint
 05151                    if (doDeintH2645)
 5152                    {
 05153                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05154                        overlayFilters.Add(deintFilter);
 5155                    }
 5156                }
 5157            }
 5158
 05159            return (mainFilters, subFilters, overlayFilters);
 5160        }
 5161
 5162        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiLimitedVidFilter
 5163            EncodingJobInfo state,
 5164            EncodingOptions options,
 5165            string vidDecoder,
 5166            string vidEncoder)
 5167        {
 05168            var inW = state.VideoStream?.Width;
 05169            var inH = state.VideoStream?.Height;
 05170            var reqW = state.BaseRequest.Width;
 05171            var reqH = state.BaseRequest.Height;
 05172            var reqMaxW = state.BaseRequest.MaxWidth;
 05173            var reqMaxH = state.BaseRequest.MaxHeight;
 05174            var threeDFormat = state.MediaSource.Video3DFormat;
 5175
 05176            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05177            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05178            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05179            var isSwEncoder = !isVaapiEncoder;
 05180            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05181            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 05182            var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965;
 05183            var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd;
 5184
 05185            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05186            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05187            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05188            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5189
 05190            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05191            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05192            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 5193
 05194            var rotation = state.VideoStream?.Rotation ?? 0;
 05195            var swapWAndH = Math.Abs(rotation) == 90 && isSwDecoder;
 05196            var swpInW = swapWAndH ? inH : inW;
 05197            var swpInH = swapWAndH ? inW : inH;
 5198
 5199            /* Make main filters for video stream */
 05200            var mainFilters = new List<string>();
 5201
 05202            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5203
 05204            var outFormat = string.Empty;
 05205            if (isSwDecoder)
 5206            {
 5207                // INPUT sw surface(memory)
 5208                // sw deint
 05209                if (doDeintH2645)
 5210                {
 05211                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05212                    mainFilters.Add(swDeintFilter);
 5213                }
 5214
 05215                outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05216                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05217                if (isMjpegEncoder && !doOclTonemap)
 5218                {
 5219                    // sw decoder + hw mjpeg encoder
 05220                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5221                }
 5222
 5223                // sw scale
 05224                mainFilters.Add(swScaleFilter);
 05225                mainFilters.Add("format=" + outFormat);
 5226
 5227                // keep video at memory except ocl tonemap,
 5228                // since the overhead caused by hwupload >>> using sw filter.
 5229                // sw => hw
 05230                if (doOclTonemap)
 5231                {
 05232                    mainFilters.Add("hwupload=derive_device=opencl");
 5233                }
 5234            }
 05235            else if (isVaapiDecoder)
 5236            {
 5237                // INPUT vaapi surface(vram)
 5238                // hw deint
 05239                if (doDeintH2645)
 5240                {
 05241                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05242                    mainFilters.Add(deintFilter);
 5243                }
 5244
 05245                outFormat = doOclTonemap ? string.Empty : "nv12";
 05246                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, inW, inH, reqW, reqH, reqMaxW, 
 5247
 05248                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5249                {
 05250                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05251                    hwScaleFilter += ":mode=hq";
 5252                }
 5253
 5254                // allocate extra pool sizes for vaapi vpp
 05255                if (!string.IsNullOrEmpty(hwScaleFilter))
 5256                {
 05257                    hwScaleFilter += ":extra_hw_frames=24";
 5258                }
 5259
 5260                // hw scale
 05261                mainFilters.Add(hwScaleFilter);
 5262            }
 5263
 05264            if (doOclTonemap && isVaapiDecoder)
 5265            {
 05266                if (isi965Driver)
 5267                {
 5268                    // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05269                    mainFilters.Add("hwmap=derive_device=opencl");
 5270                }
 5271                else
 5272                {
 05273                    mainFilters.Add("hwdownload");
 05274                    mainFilters.Add("format=p010le");
 05275                    mainFilters.Add("hwupload=derive_device=opencl");
 5276                }
 5277            }
 5278
 5279            // ocl tonemap
 05280            if (doOclTonemap)
 5281            {
 05282                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05283                mainFilters.Add(tonemapFilter);
 5284            }
 5285
 05286            if (doOclTonemap && isVaInVaOut)
 5287            {
 05288                if (isi965Driver)
 5289                {
 5290                    // OUTPUT vaapi(nv12) surface(vram)
 5291                    // reverse-mapping via vaapi-opencl interop.
 05292                    mainFilters.Add("hwmap=derive_device=vaapi:reverse=1");
 05293                    mainFilters.Add("format=vaapi");
 5294                }
 5295            }
 5296
 05297            var memoryOutput = false;
 05298            var isUploadForOclTonemap = doOclTonemap && (isSwDecoder || (isVaapiDecoder && !isi965Driver));
 05299            var isHwmapNotUsable = hasGraphicalSubs || isUploadForOclTonemap;
 05300            var isHwmapForSubs = hasSubs && isVaapiDecoder;
 05301            var isHwUnmapForTextSubs = hasTextSubs && isVaInVaOut && !isUploadForOclTonemap;
 05302            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap || isHwmapForSubs)
 5303            {
 05304                memoryOutput = true;
 5305
 5306                // OUTPUT nv12 surface(memory)
 5307                // prefer hwmap to hwdownload on opencl/vaapi.
 05308                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap");
 05309                mainFilters.Add("format=nv12");
 5310            }
 5311
 5312            // OUTPUT nv12 surface(memory)
 05313            if (isSwDecoder && isVaapiEncoder)
 5314            {
 05315                memoryOutput = true;
 5316            }
 5317
 05318            if (memoryOutput)
 5319            {
 5320                // text subtitles
 05321                if (hasTextSubs)
 5322                {
 05323                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05324                    mainFilters.Add(textSubtitlesFilter);
 5325                }
 5326            }
 5327
 05328            if (isHwUnmapForTextSubs)
 5329            {
 05330                mainFilters.Add("hwmap");
 05331                mainFilters.Add("format=vaapi");
 5332            }
 05333            else if (memoryOutput && isVaapiEncoder)
 5334            {
 05335                if (!hasGraphicalSubs)
 5336                {
 05337                    mainFilters.Add("hwupload_vaapi");
 5338                }
 5339            }
 5340
 5341            /* Make sub and overlay filters for subtitle stream */
 05342            var subFilters = new List<string>();
 05343            var overlayFilters = new List<string>();
 05344            if (memoryOutput)
 5345            {
 05346                if (hasGraphicalSubs)
 5347                {
 05348                    var subW = state.SubtitleStream?.Width;
 05349                    var subH = state.SubtitleStream?.Height;
 05350                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05351                    subFilters.Add(subPreProcFilters);
 05352                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5353
 05354                    if (isVaapiEncoder)
 5355                    {
 05356                        overlayFilters.Add("hwupload_vaapi");
 5357                    }
 5358                }
 5359            }
 5360
 05361            return (mainFilters, subFilters, overlayFilters);
 5362        }
 5363
 5364        /// <summary>
 5365        /// Gets the parameter of Apple VideoToolBox filter chain.
 5366        /// </summary>
 5367        /// <param name="state">Encoding state.</param>
 5368        /// <param name="options">Encoding options.</param>
 5369        /// <param name="vidEncoder">Video encoder to use.</param>
 5370        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5371        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFilterChain(
 5372            EncodingJobInfo state,
 5373            EncodingOptions options,
 5374            string vidEncoder)
 5375        {
 05376            if (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 5377            {
 05378                return (null, null, null);
 5379            }
 5380
 5381            // ReSharper disable once InconsistentNaming
 05382            var isMacOS = OperatingSystem.IsMacOS();
 05383            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05384            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05385            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05386            var isVtFullSupported = isMacOS && IsVideoToolboxFullSupported();
 5387
 5388            // legacy videotoolbox pipeline (disable hw filters)
 05389            if (!(isVtEncoder || isVtDecoder)
 05390                || !isVtFullSupported
 05391                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5392            {
 05393                return GetSwVidFilterChain(state, options, vidEncoder);
 5394            }
 5395
 5396            // preferred videotoolbox + metal filters pipeline
 05397            return GetAppleVidFiltersPreferred(state, options, vidDecoder, vidEncoder);
 5398        }
 5399
 5400        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFiltersPrefer
 5401            EncodingJobInfo state,
 5402            EncodingOptions options,
 5403            string vidDecoder,
 5404            string vidEncoder)
 5405        {
 05406            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05407            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05408            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5409
 05410            var inW = state.VideoStream?.Width;
 05411            var inH = state.VideoStream?.Height;
 05412            var reqW = state.BaseRequest.Width;
 05413            var reqH = state.BaseRequest.Height;
 05414            var reqMaxW = state.BaseRequest.MaxWidth;
 05415            var reqMaxH = state.BaseRequest.MaxHeight;
 05416            var threeDFormat = state.MediaSource.Video3DFormat;
 5417
 05418            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05419            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05420            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05421            var doVtTonemap = IsVideoToolboxTonemapAvailable(state, options);
 05422            var doMetalTonemap = !doVtTonemap && IsHwTonemapAvailable(state, options);
 05423            var usingHwSurface = isVtDecoder && (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface);
 5424
 05425            var rotation = state.VideoStream?.Rotation ?? 0;
 05426            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05427            var doVtTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_vt");
 05428            var swapWAndH = Math.Abs(rotation) == 90 && doVtTranspose;
 05429            var swpInW = swapWAndH ? inH : inW;
 05430            var swpInH = swapWAndH ? inW : inH;
 5431
 05432            var scaleFormat = string.Empty;
 5433            // Use P010 for Metal tone mapping, otherwise force an 8bit output.
 05434            if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase))
 5435            {
 05436                if (doMetalTonemap)
 5437                {
 05438                    if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 5439                    {
 05440                        scaleFormat = "p010le";
 5441                    }
 5442                }
 5443                else
 5444                {
 05445                    scaleFormat = "nv12";
 5446                }
 5447            }
 5448
 05449            var hwScaleFilter = GetHwScaleFilter("scale", "vt", scaleFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW,
 5450
 05451            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05452            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05453            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05454            var hasAssSubs = hasSubs
 05455                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05456                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5457
 5458            /* Make main filters for video stream */
 05459            var mainFilters = new List<string>();
 5460
 5461            // hw deint
 05462            if (doDeintH2645)
 5463            {
 05464                var deintFilter = GetHwDeinterlaceFilter(state, options, "videotoolbox");
 05465                mainFilters.Add(deintFilter);
 5466            }
 5467
 5468            // hw transpose
 05469            if (doVtTranspose)
 5470            {
 05471                mainFilters.Add($"transpose_vt=dir={transposeDir}");
 5472            }
 5473
 05474            if (doVtTonemap)
 5475            {
 5476                const string VtTonemapArgs = "color_matrix=bt709:color_primaries=bt709:color_transfer=bt709";
 5477
 5478                // scale_vt can handle scaling & tonemapping in one shot, just like vpp_qsv.
 05479                hwScaleFilter = string.IsNullOrEmpty(hwScaleFilter)
 05480                    ? "scale_vt=" + VtTonemapArgs
 05481                    : hwScaleFilter + ":" + VtTonemapArgs;
 5482            }
 5483
 5484            // hw scale & vt tonemap
 05485            mainFilters.Add(hwScaleFilter);
 5486
 5487            // Metal tonemap
 05488            if (doMetalTonemap)
 5489            {
 05490                var tonemapFilter = GetHwTonemapFilter(options, "videotoolbox", "nv12", isMjpegEncoder);
 05491                mainFilters.Add(tonemapFilter);
 5492            }
 5493
 5494            /* Make sub and overlay filters for subtitle stream */
 05495            var subFilters = new List<string>();
 05496            var overlayFilters = new List<string>();
 5497
 05498            if (hasSubs)
 5499            {
 05500                if (hasGraphicalSubs)
 5501                {
 05502                    var subW = state.SubtitleStream?.Width;
 05503                    var subH = state.SubtitleStream?.Height;
 05504                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05505                    subFilters.Add(subPreProcFilters);
 05506                    subFilters.Add("format=bgra");
 5507                }
 05508                else if (hasTextSubs)
 5509                {
 05510                    var framerate = state.VideoStream?.RealFrameRate;
 05511                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5512
 05513                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05514                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05515                    subFilters.Add(alphaSrcFilter);
 05516                    subFilters.Add("format=bgra");
 05517                    subFilters.Add(subTextSubtitlesFilter);
 5518                }
 5519
 05520                subFilters.Add("hwupload");
 05521                overlayFilters.Add("overlay_videotoolbox=eof_action=pass:repeatlast=0");
 5522            }
 5523
 05524            if (usingHwSurface)
 5525            {
 05526                if (!isVtEncoder)
 5527                {
 05528                    mainFilters.Add("hwdownload");
 05529                    mainFilters.Add("format=nv12");
 5530                }
 5531
 05532                return (mainFilters, subFilters, overlayFilters);
 5533            }
 5534
 5535            // For old jellyfin-ffmpeg that has broken hwsurface, add a hwupload
 05536            var needFiltering = mainFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05537                                subFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05538                                overlayFilters.Any(f => !string.IsNullOrEmpty(f));
 05539            if (needFiltering)
 5540            {
 5541                // INPUT videotoolbox/memory surface(vram/uma)
 5542                // this will pass-through automatically if in/out format matches.
 05543                mainFilters.Insert(0, "hwupload");
 05544                mainFilters.Insert(0, "format=nv12|p010le|videotoolbox_vld");
 5545
 05546                if (!isVtEncoder)
 5547                {
 05548                    mainFilters.Add("hwdownload");
 05549                    mainFilters.Add("format=nv12");
 5550                }
 5551            }
 5552
 05553            return (mainFilters, subFilters, overlayFilters);
 5554        }
 5555
 5556        /// <summary>
 5557        /// Gets the parameter of Rockchip RKMPP/RKRGA filter chain.
 5558        /// </summary>
 5559        /// <param name="state">Encoding state.</param>
 5560        /// <param name="options">Encoding options.</param>
 5561        /// <param name="vidEncoder">Video encoder to use.</param>
 5562        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5563        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFilterChain(
 5564            EncodingJobInfo state,
 5565            EncodingOptions options,
 5566            string vidEncoder)
 5567        {
 05568            if (options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 5569            {
 05570                return (null, null, null);
 5571            }
 5572
 05573            var isLinux = OperatingSystem.IsLinux();
 05574            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05575            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05576            var isSwEncoder = !vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05577            var isRkmppOclSupported = isLinux && IsRkmppFullSupported() && IsOpenclFullSupported();
 5578
 05579            if ((isSwDecoder && isSwEncoder)
 05580                || !isRkmppOclSupported
 05581                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5582            {
 05583                return GetSwVidFilterChain(state, options, vidEncoder);
 5584            }
 5585
 5586            // preferred rkmpp + rkrga + opencl filters pipeline
 05587            if (isRkmppOclSupported)
 5588            {
 05589                return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5590            }
 5591
 05592            return (null, null, null);
 5593        }
 5594
 5595        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFiltersPrefer
 5596            EncodingJobInfo state,
 5597            EncodingOptions options,
 5598            string vidDecoder,
 5599            string vidEncoder)
 5600        {
 05601            var inW = state.VideoStream?.Width;
 05602            var inH = state.VideoStream?.Height;
 05603            var reqW = state.BaseRequest.Width;
 05604            var reqH = state.BaseRequest.Height;
 05605            var reqMaxW = state.BaseRequest.MaxWidth;
 05606            var reqMaxH = state.BaseRequest.MaxHeight;
 05607            var threeDFormat = state.MediaSource.Video3DFormat;
 5608
 05609            var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05610            var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05611            var isSwDecoder = !isRkmppDecoder;
 05612            var isSwEncoder = !isRkmppEncoder;
 05613            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05614            var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder;
 05615            var isEncoderSupportAfbc = isRkmppEncoder
 05616                && (vidEncoder.Contains("h264", StringComparison.OrdinalIgnoreCase)
 05617                    || vidEncoder.Contains("hevc", StringComparison.OrdinalIgnoreCase));
 5618
 05619            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05620            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05621            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05622            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5623
 05624            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05625            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05626            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05627            var hasAssSubs = hasSubs
 05628                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05629                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 05630            var subW = state.SubtitleStream?.Width;
 05631            var subH = state.SubtitleStream?.Height;
 5632
 05633            var rotation = state.VideoStream?.Rotation ?? 0;
 05634            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05635            var doRkVppTranspose = !string.IsNullOrEmpty(transposeDir);
 05636            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isRkmppDecoder && doRkVppTranspose));
 05637            var swpInW = swapWAndH ? inH : inW;
 05638            var swpInH = swapWAndH ? inW : inH;
 5639
 5640            /* Make main filters for video stream */
 05641            var mainFilters = new List<string>();
 5642
 05643            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5644
 05645            if (isSwDecoder)
 5646            {
 5647                // INPUT sw surface(memory)
 5648                // sw deint
 05649                if (doDeintH2645)
 5650                {
 05651                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05652                    mainFilters.Add(swDeintFilter);
 5653                }
 5654
 05655                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 05656                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05657                if (isMjpegEncoder && !doOclTonemap)
 5658                {
 5659                    // sw decoder + hw mjpeg encoder
 05660                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5661                }
 5662
 05663                if (!string.IsNullOrEmpty(swScaleFilter))
 5664                {
 05665                    swScaleFilter += ":flags=fast_bilinear";
 5666                }
 5667
 5668                // sw scale
 05669                mainFilters.Add(swScaleFilter);
 05670                mainFilters.Add($"format={outFormat}");
 5671
 5672                // keep video at memory except ocl tonemap,
 5673                // since the overhead caused by hwupload >>> using sw filter.
 5674                // sw => hw
 05675                if (doOclTonemap)
 5676                {
 05677                    mainFilters.Add("hwupload=derive_device=opencl");
 5678                }
 5679            }
 05680            else if (isRkmppDecoder)
 5681            {
 5682                // INPUT rkmpp/drm surface(gem/dma-heap)
 5683
 05684                var isFullAfbcPipeline = isEncoderSupportAfbc && isDrmInDrmOut && !doOclTonemap;
 05685                var swapOutputWandH = doRkVppTranspose && swapWAndH;
 05686                var outFormat = doOclTonemap ? "p010" : (isMjpegEncoder ? "bgra" : "nv12"); // RGA only support full ran
 05687                var hwScaleFilter = GetHwScaleFilter("vpp", "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, r
 05688                var doScaling = GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, swpInW, swpInH, reqW, re
 5689
 05690                if (!hasSubs
 05691                     || doRkVppTranspose
 05692                     || !isFullAfbcPipeline
 05693                     || !string.IsNullOrEmpty(doScaling))
 5694                {
 5695                    // RGA3 hardware only support (1/8 ~ 8) scaling in each blit operation,
 5696                    // but in Trickplay there's a case: (3840/320 == 12), enable 2pass for it
 05697                    if (!string.IsNullOrEmpty(doScaling)
 05698                        && !IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f))
 5699                    {
 5700                        // Vendor provided BSP kernel has an RGA driver bug that causes the output to be corrupted for P
 5701                        // Use NV15 instead of P010 to avoid the issue.
 5702                        // SDR inputs are using BGRA formats already which is not affected.
 05703                        var intermediateFormat = string.Equals(outFormat, "p010", StringComparison.OrdinalIgnoreCase) ? 
 05704                        var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={intermediateFormat}:force_d
 05705                        mainFilters.Add(hwScaleFilterFirstPass);
 5706                    }
 5707
 05708                    if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose)
 5709                    {
 05710                        hwScaleFilter += $":transpose={transposeDir}";
 5711                    }
 5712
 5713                    // try enabling AFBC to save DDR bandwidth
 05714                    if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline)
 5715                    {
 05716                        hwScaleFilter += ":afbc=1";
 5717                    }
 5718
 5719                    // hw transpose & scale
 05720                    mainFilters.Add(hwScaleFilter);
 5721                }
 5722            }
 5723
 05724            if (doOclTonemap && isRkmppDecoder)
 5725            {
 5726                // map from rkmpp/drm to opencl via drm-opencl interop.
 05727                mainFilters.Add("hwmap=derive_device=opencl");
 5728            }
 5729
 5730            // ocl tonemap
 05731            if (doOclTonemap)
 5732            {
 05733                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05734                mainFilters.Add(tonemapFilter);
 5735            }
 5736
 05737            var memoryOutput = false;
 05738            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 05739            if ((isRkmppDecoder && isSwEncoder) || isUploadForOclTonemap)
 5740            {
 05741                memoryOutput = true;
 5742
 5743                // OUTPUT nv12 surface(memory)
 05744                mainFilters.Add("hwdownload");
 05745                mainFilters.Add("format=nv12");
 5746            }
 5747
 5748            // OUTPUT nv12 surface(memory)
 05749            if (isSwDecoder && isRkmppEncoder)
 5750            {
 05751                memoryOutput = true;
 5752            }
 5753
 05754            if (memoryOutput)
 5755            {
 5756                // text subtitles
 05757                if (hasTextSubs)
 5758                {
 05759                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05760                    mainFilters.Add(textSubtitlesFilter);
 5761                }
 5762            }
 5763
 05764            if (isDrmInDrmOut)
 5765            {
 05766                if (doOclTonemap)
 5767                {
 5768                    // OUTPUT drm(nv12) surface(gem/dma-heap)
 5769                    // reverse-mapping via drm-opencl interop.
 05770                    mainFilters.Add("hwmap=derive_device=rkmpp:reverse=1");
 05771                    mainFilters.Add("format=drm_prime");
 5772                }
 5773            }
 5774
 5775            /* Make sub and overlay filters for subtitle stream */
 05776            var subFilters = new List<string>();
 05777            var overlayFilters = new List<string>();
 05778            if (isDrmInDrmOut)
 5779            {
 05780                if (hasSubs)
 5781                {
 05782                    if (hasGraphicalSubs)
 5783                    {
 05784                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 05785                        subFilters.Add(subPreProcFilters);
 05786                        subFilters.Add("format=bgra");
 5787                    }
 05788                    else if (hasTextSubs)
 5789                    {
 05790                        var framerate = state.VideoStream?.RealFrameRate;
 05791                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5792
 5793                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 05794                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 05795                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05796                        subFilters.Add(alphaSrcFilter);
 05797                        subFilters.Add("format=bgra");
 05798                        subFilters.Add(subTextSubtitlesFilter);
 5799                    }
 5800
 05801                    subFilters.Add("hwupload=derive_device=rkmpp");
 5802
 5803                    // try enabling AFBC to save DDR bandwidth
 05804                    var hwOverlayFilter = "overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12";
 05805                    if (isEncoderSupportAfbc)
 5806                    {
 05807                        hwOverlayFilter += ":afbc=1";
 5808                    }
 5809
 05810                    overlayFilters.Add(hwOverlayFilter);
 5811                }
 5812            }
 05813            else if (memoryOutput)
 5814            {
 05815                if (hasGraphicalSubs)
 5816                {
 05817                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05818                    subFilters.Add(subPreProcFilters);
 05819                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5820                }
 5821            }
 5822
 05823            return (mainFilters, subFilters, overlayFilters);
 5824        }
 5825
 5826        /// <summary>
 5827        /// Gets the parameter of video processing filters.
 5828        /// </summary>
 5829        /// <param name="state">Encoding state.</param>
 5830        /// <param name="options">Encoding options.</param>
 5831        /// <param name="outputVideoCodec">Video codec to use.</param>
 5832        /// <returns>The video processing filters parameter.</returns>
 5833        public string GetVideoProcessingFilterParam(
 5834            EncodingJobInfo state,
 5835            EncodingOptions options,
 5836            string outputVideoCodec)
 5837        {
 05838            var videoStream = state.VideoStream;
 05839            if (videoStream is null)
 5840            {
 05841                return string.Empty;
 5842            }
 5843
 05844            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05845            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05846            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 5847
 5848            List<string> mainFilters;
 5849            List<string> subFilters;
 5850            List<string> overlayFilters;
 5851
 05852            (mainFilters, subFilters, overlayFilters) = options.HardwareAccelerationType switch
 05853            {
 05854                HardwareAccelerationType.vaapi => GetVaapiVidFilterChain(state, options, outputVideoCodec),
 05855                HardwareAccelerationType.amf => GetAmdVidFilterChain(state, options, outputVideoCodec),
 05856                HardwareAccelerationType.qsv => GetIntelVidFilterChain(state, options, outputVideoCodec),
 05857                HardwareAccelerationType.nvenc => GetNvidiaVidFilterChain(state, options, outputVideoCodec),
 05858                HardwareAccelerationType.videotoolbox => GetAppleVidFilterChain(state, options, outputVideoCodec),
 05859                HardwareAccelerationType.rkmpp => GetRkmppVidFilterChain(state, options, outputVideoCodec),
 05860                _ => GetSwVidFilterChain(state, options, outputVideoCodec),
 05861            };
 5862
 05863            mainFilters?.RemoveAll(string.IsNullOrEmpty);
 05864            subFilters?.RemoveAll(string.IsNullOrEmpty);
 05865            overlayFilters?.RemoveAll(string.IsNullOrEmpty);
 5866
 05867            var framerate = GetFramerateParam(state);
 05868            if (framerate.HasValue)
 5869            {
 05870                mainFilters.Insert(0, string.Format(
 05871                    CultureInfo.InvariantCulture,
 05872                    "fps={0}",
 05873                    framerate.Value));
 5874            }
 5875
 05876            var mainStr = string.Empty;
 05877            if (mainFilters?.Count > 0)
 5878            {
 05879                mainStr = string.Format(
 05880                    CultureInfo.InvariantCulture,
 05881                    "{0}",
 05882                    string.Join(',', mainFilters));
 5883            }
 5884
 05885            if (overlayFilters?.Count == 0)
 5886            {
 5887                // -vf "scale..."
 05888                return string.IsNullOrEmpty(mainStr) ? string.Empty : " -vf \"" + mainStr + "\"";
 5889            }
 5890
 05891            if (overlayFilters?.Count > 0
 05892                && subFilters?.Count > 0
 05893                && state.SubtitleStream is not null)
 5894            {
 5895                // overlay graphical/text subtitles
 05896                var subStr = string.Format(
 05897                        CultureInfo.InvariantCulture,
 05898                        "{0}",
 05899                        string.Join(',', subFilters));
 5900
 05901                var overlayStr = string.Format(
 05902                        CultureInfo.InvariantCulture,
 05903                        "{0}",
 05904                        string.Join(',', overlayFilters));
 5905
 05906                var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
 05907                var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 05908                var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 5909
 05910                if (hasSubs)
 5911                {
 5912                    // -filter_complex "[0:s]scale=s[sub]..."
 05913                    var filterStr = string.IsNullOrEmpty(mainStr)
 05914                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]{5}\""
 05915                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 5916
 05917                    if (hasTextSubs)
 5918                    {
 05919                        filterStr = string.IsNullOrEmpty(mainStr)
 05920                            ? " -filter_complex \"{4}[sub];[0:{2}][sub]{5}\""
 05921                            : " -filter_complex \"{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 5922                    }
 5923
 05924                    return string.Format(
 05925                        CultureInfo.InvariantCulture,
 05926                        filterStr,
 05927                        mapPrefix,
 05928                        subtitleStreamIndex,
 05929                        videoStreamIndex,
 05930                        mainStr,
 05931                        subStr,
 05932                        overlayStr);
 5933                }
 5934            }
 5935
 05936            return string.Empty;
 5937        }
 5938
 5939        public string GetOverwriteColorPropertiesParam(EncodingJobInfo state, bool isTonemapAvailable)
 5940        {
 05941            if (isTonemapAvailable)
 5942            {
 05943                return GetInputHdrParam(state.VideoStream?.ColorTransfer);
 5944            }
 5945
 05946            return GetOutputSdrParam(null);
 5947        }
 5948
 5949        public string GetInputHdrParam(string colorTransfer)
 5950        {
 05951            if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
 5952            {
 5953                // HLG
 05954                return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
 5955            }
 5956
 5957            // HDR10
 05958            return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc";
 5959        }
 5960
 5961        public string GetOutputSdrParam(string tonemappingRange)
 5962        {
 5963            // SDR
 05964            if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
 5965            {
 05966                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
 5967            }
 5968
 05969            if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
 5970            {
 05971                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc";
 5972            }
 5973
 05974            return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
 5975        }
 5976
 5977        public static int GetVideoColorBitDepth(EncodingJobInfo state)
 5978        {
 05979            var videoStream = state.VideoStream;
 05980            if (videoStream is not null)
 5981            {
 05982                if (videoStream.BitDepth.HasValue)
 5983                {
 05984                    return videoStream.BitDepth.Value;
 5985                }
 5986
 05987                if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
 05988                    || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
 05989                    || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 05990                    || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
 5991                {
 05992                    return 8;
 5993                }
 5994
 05995                if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 05996                    || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 05997                    || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
 5998                {
 05999                    return 10;
 6000                }
 6001
 06002                if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 06003                    || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 06004                    || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
 6005                {
 06006                    return 12;
 6007                }
 6008
 06009                return 8;
 6010            }
 6011
 06012            return 0;
 6013        }
 6014
 6015        /// <summary>
 6016        /// Gets the ffmpeg option string for the hardware accelerated video decoder.
 6017        /// </summary>
 6018        /// <param name="state">The encoding job info.</param>
 6019        /// <param name="options">The encoding options.</param>
 6020        /// <returns>The option string or null if none available.</returns>
 6021        protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
 6022        {
 06023            var videoStream = state.VideoStream;
 06024            var mediaSource = state.MediaSource;
 06025            if (videoStream is null || mediaSource is null)
 6026            {
 06027                return null;
 6028            }
 6029
 6030            // HWA decoders can handle both video files and video folders.
 06031            var videoType = state.VideoType;
 06032            if (videoType != VideoType.VideoFile
 06033                && videoType != VideoType.Iso
 06034                && videoType != VideoType.Dvd
 06035                && videoType != VideoType.BluRay)
 6036            {
 06037                return null;
 6038            }
 6039
 06040            if (IsCopyCodec(state.OutputVideoCodec))
 6041            {
 06042                return null;
 6043            }
 6044
 06045            var hardwareAccelerationType = options.HardwareAccelerationType;
 6046
 06047            if (!string.IsNullOrEmpty(videoStream.Codec) && hardwareAccelerationType != HardwareAccelerationType.none)
 6048            {
 06049                var bitDepth = GetVideoColorBitDepth(state);
 6050
 6051                // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support for most platforms
 06052                if (bitDepth == 10
 06053                    && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06054                         || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
 06055                         || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)
 06056                         || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)))
 6057                {
 6058                    // RKMPP has H.264 Hi10P decoder
 06059                    bool hasHardwareHi10P = hardwareAccelerationType == HardwareAccelerationType.rkmpp;
 6060
 6061                    // VideoToolbox on Apple Silicon has H.264 Hi10P mode enabled after macOS 14.6
 06062                    if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox)
 6063                    {
 06064                        var ver = Environment.OSVersion.Version;
 06065                        var arch = RuntimeInformation.OSArchitecture;
 06066                        if (arch.Equals(Architecture.Arm64) && ver >= new Version(14, 6))
 6067                        {
 06068                            hasHardwareHi10P = true;
 6069                        }
 6070                    }
 6071
 06072                    if (!hasHardwareHi10P
 06073                        && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6074                    {
 06075                        return null;
 6076                    }
 6077                }
 6078
 06079                var decoder = hardwareAccelerationType switch
 06080                {
 06081                    HardwareAccelerationType.vaapi => GetVaapiVidDecoder(state, options, videoStream, bitDepth),
 06082                    HardwareAccelerationType.amf => GetAmfVidDecoder(state, options, videoStream, bitDepth),
 06083                    HardwareAccelerationType.qsv => GetQsvHwVidDecoder(state, options, videoStream, bitDepth),
 06084                    HardwareAccelerationType.nvenc => GetNvdecVidDecoder(state, options, videoStream, bitDepth),
 06085                    HardwareAccelerationType.videotoolbox => GetVideotoolboxVidDecoder(state, options, videoStream, bitD
 06086                    HardwareAccelerationType.rkmpp => GetRkmppVidDecoder(state, options, videoStream, bitDepth),
 06087                    _ => string.Empty
 06088                };
 6089
 06090                if (!string.IsNullOrEmpty(decoder))
 6091                {
 06092                    return decoder;
 6093                }
 6094            }
 6095
 6096            // leave blank so ffmpeg will decide
 06097            return null;
 6098        }
 6099
 6100        /// <summary>
 6101        /// Gets a hw decoder name.
 6102        /// </summary>
 6103        /// <param name="options">Encoding options.</param>
 6104        /// <param name="decoderPrefix">Decoder prefix.</param>
 6105        /// <param name="decoderSuffix">Decoder suffix.</param>
 6106        /// <param name="videoCodec">Video codec to use.</param>
 6107        /// <param name="bitDepth">Video color bit depth.</param>
 6108        /// <returns>Hardware decoder name.</returns>
 6109        public string GetHwDecoderName(EncodingOptions options, string decoderPrefix, string decoderSuffix, string video
 6110        {
 06111            if (string.IsNullOrEmpty(decoderPrefix) || string.IsNullOrEmpty(decoderSuffix))
 6112            {
 06113                return null;
 6114            }
 6115
 06116            var decoderName = decoderPrefix + '_' + decoderSuffix;
 6117
 06118            var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains
 6119
 6120            // VideoToolbox decoders have built-in SW fallback
 06121            if (bitDepth == 10
 06122                && isCodecAvailable
 06123                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6124            {
 06125                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06126                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
 06127                    && !options.EnableDecodingColorDepth10Hevc)
 6128                {
 06129                    return null;
 6130                }
 6131
 06132                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06133                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06134                    && !options.EnableDecodingColorDepth10Vp9)
 6135                {
 06136                    return null;
 6137                }
 6138            }
 6139
 06140            if (string.Equals(decoderSuffix, "cuvid", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdec
 6141            {
 06142                return null;
 6143            }
 6144
 06145            if (string.Equals(decoderSuffix, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwD
 6146            {
 06147                return null;
 6148            }
 6149
 06150            if (string.Equals(decoderSuffix, "rkmpp", StringComparison.OrdinalIgnoreCase))
 6151            {
 06152                return null;
 6153            }
 6154
 06155            return isCodecAvailable ? (" -c:v " + decoderName) : null;
 6156        }
 6157
 6158        /// <summary>
 6159        /// Gets a hwaccel type to use as a hardware decoder depending on the system.
 6160        /// </summary>
 6161        /// <param name="state">Encoding state.</param>
 6162        /// <param name="options">Encoding options.</param>
 6163        /// <param name="videoCodec">Video codec to use.</param>
 6164        /// <param name="bitDepth">Video color bit depth.</param>
 6165        /// <param name="outputHwSurface">Specifies if output hw surface.</param>
 6166        /// <returns>Hardware accelerator type.</returns>
 6167        public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, int bitDepth, bo
 6168        {
 06169            var isWindows = OperatingSystem.IsWindows();
 06170            var isLinux = OperatingSystem.IsLinux();
 06171            var isMacOS = OperatingSystem.IsMacOS();
 06172            var isD3d11Supported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va");
 06173            var isVaapiSupported = isLinux && IsVaapiSupported(state);
 06174            var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported();
 06175            var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv");
 06176            var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
 06177            var isRkmppSupported = isLinux && IsRkmppFullSupported();
 06178            var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCas
 06179            var hardwareAccelerationType = options.HardwareAccelerationType;
 6180
 06181            var ffmpegVersion = _mediaEncoder.EncoderVersion;
 6182
 6183            // Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
 06184            var isAv1 = ffmpegVersion < _minFFmpegImplicitHwaccel
 06185                && string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
 6186
 6187            // Allow profile mismatch if decoding H.264 baseline with d3d11va and vaapi hwaccels.
 06188            var profileMismatch = string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
 06189                && string.Equals(state.VideoStream?.Profile, "baseline", StringComparison.OrdinalIgnoreCase);
 6190
 6191            // Disable the extra internal copy in nvdec. We already handle it in filter chain.
 06192            var nvdecNoInternalCopy = ffmpegVersion >= _minFFmpegHwaUnsafeOutput;
 6193
 6194            // Strip the display rotation side data from the transposed fmp4 output stream.
 06195            var stripRotationData = (state.VideoStream?.Rotation ?? 0) != 0
 06196                && ffmpegVersion >= _minFFmpegDisplayRotationOption;
 06197            var stripRotationDataArgs = stripRotationData ? " -display_rotation 0" : string.Empty;
 6198
 6199            // VideoToolbox decoders have built-in SW fallback
 06200            if (isCodecAvailable
 06201                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6202            {
 06203                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06204                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase))
 6205                {
 06206                    if (IsVideoStreamHevcRext(state))
 6207                    {
 06208                        if (bitDepth <= 10 && !options.EnableDecodingColorDepth10HevcRext)
 6209                        {
 06210                            return null;
 6211                        }
 6212
 06213                        if (bitDepth == 12 && !options.EnableDecodingColorDepth12HevcRext)
 6214                        {
 06215                            return null;
 6216                        }
 6217
 06218                        if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06219                            && !_mediaEncoder.IsVaapiDeviceInteliHD)
 6220                        {
 06221                            return null;
 6222                        }
 6223                    }
 06224                    else if (bitDepth == 10 && !options.EnableDecodingColorDepth10Hevc)
 6225                    {
 06226                        return null;
 6227                    }
 6228                }
 6229
 06230                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06231                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06232                    && bitDepth == 10
 06233                    && !options.EnableDecodingColorDepth10Vp9)
 6234                {
 06235                    return null;
 6236                }
 6237            }
 6238
 6239            // Intel qsv/d3d11va/vaapi
 06240            if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 6241            {
 06242                if (options.PreferSystemNativeHwDecoder)
 6243                {
 06244                    if (isVaapiSupported && isCodecAvailable)
 6245                    {
 06246                        return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + st
 06247                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " 
 6248                    }
 6249
 06250                    if (isD3d11Supported && isCodecAvailable)
 6251                    {
 06252                        return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + 
 06253                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 
 6254                    }
 6255                }
 6256                else
 6257                {
 06258                    if (isQsvSupported && isCodecAvailable)
 6259                    {
 06260                        return " -hwaccel qsv" + (outputHwSurface ? " -hwaccel_output_format qsv -noautorotate" + stripR
 6261                    }
 6262                }
 6263            }
 6264
 6265            // Nvidia cuda
 06266            if (hardwareAccelerationType == HardwareAccelerationType.nvenc)
 6267            {
 06268                if (isCudaSupported && isCodecAvailable)
 6269                {
 06270                    if (options.EnableEnhancedNvdecDecoder)
 6271                    {
 6272                        // set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support.
 06273                        return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stri
 06274                            + (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + 
 6275                    }
 6276
 6277                    // cuvid decoder doesn't have threading issue.
 06278                    return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stripRot
 6279                }
 6280            }
 6281
 6282            // Amd d3d11va
 06283            if (hardwareAccelerationType == HardwareAccelerationType.amf)
 6284            {
 06285                if (isD3d11Supported && isCodecAvailable)
 6286                {
 06287                    return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + stri
 06288                        + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v
 6289                }
 6290            }
 6291
 6292            // Vaapi
 06293            if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06294                && isVaapiSupported
 06295                && isCodecAvailable)
 6296            {
 06297                return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + stripRotat
 06298                    + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1
 6299            }
 6300
 6301            // Apple videotoolbox
 06302            if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06303                && isVideotoolboxSupported
 06304                && isCodecAvailable)
 6305            {
 06306                return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string
 6307            }
 6308
 6309            // Rockchip rkmpp
 06310            if (hardwareAccelerationType == HardwareAccelerationType.rkmpp
 06311                && isRkmppSupported
 06312                && isCodecAvailable)
 6313            {
 06314                return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime -noautorotate" + stripR
 6315            }
 6316
 06317            return null;
 6318        }
 6319
 6320        public string GetQsvHwVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6321        {
 06322            var isWindows = OperatingSystem.IsWindows();
 06323            var isLinux = OperatingSystem.IsLinux();
 6324
 06325            if ((!isWindows && !isLinux)
 06326                || options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 6327            {
 06328                return null;
 6329            }
 6330
 06331            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 06332            var isIntelDx11OclSupported = isWindows
 06333                && _mediaEncoder.SupportsHwaccel("d3d11va")
 06334                && isQsvOclSupported;
 06335            var isIntelVaapiOclSupported = isLinux
 06336                && IsVaapiSupported(state)
 06337                && isQsvOclSupported;
 06338            var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
 06339                && _mediaEncoder.SupportsFilter("alphasrc");
 6340
 06341            var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06342                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06343            var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 06344            var is8_10_12bitSwFormatsQsv = is8_10bitSwFormatsQsv
 06345                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06346                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06347                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06348                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06349                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06350                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06351                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6352            // TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
 6353
 06354            if (is8bitSwFormatsQsv)
 6355            {
 06356                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06357                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6358                {
 06359                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6360                }
 6361
 06362                if (string.Equals(videoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase))
 6363                {
 06364                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6365                }
 6366
 06367                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6368                {
 06369                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6370                }
 6371
 06372                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6373                {
 06374                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6375                }
 6376            }
 6377
 06378            if (is8_10bitSwFormatsQsv)
 6379            {
 06380                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6381                {
 06382                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6383                }
 6384
 06385                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 6386                {
 06387                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6388                }
 6389            }
 6390
 06391            if (is8_10_12bitSwFormatsQsv)
 6392            {
 06393                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06394                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6395                {
 06396                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6397                }
 6398            }
 6399
 06400            return null;
 6401        }
 6402
 6403        public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6404        {
 06405            if ((!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
 06406                || options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 6407            {
 06408                return null;
 6409            }
 6410
 06411            var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
 06412            var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06413                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06414            var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06415            var is8_10_12bitSwFormatsNvdec = is8_10bitSwFormatsNvdec
 06416                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06417                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06418                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06419                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6420            // TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
 6421
 06422            if (is8bitSwFormatsNvdec)
 6423            {
 06424                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06425                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6426                {
 06427                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6428                }
 6429
 06430                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6431                {
 06432                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6433                }
 6434
 06435                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6436                {
 06437                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6438                }
 6439
 06440                if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6441                {
 06442                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpe
 6443                }
 6444
 06445                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6446                {
 06447                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6448                }
 6449            }
 6450
 06451            if (is8_10bitSwFormatsNvdec)
 6452            {
 06453                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6454                {
 06455                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6456                }
 6457
 06458                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6459                {
 06460                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6461                }
 6462            }
 6463
 06464            if (is8_10_12bitSwFormatsNvdec)
 6465            {
 06466                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06467                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6468                {
 06469                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6470                }
 6471            }
 6472
 06473            return null;
 6474        }
 6475
 6476        public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitD
 6477        {
 06478            if (!OperatingSystem.IsWindows()
 06479                || options.HardwareAccelerationType != HardwareAccelerationType.amf)
 6480            {
 06481                return null;
 6482            }
 6483
 06484            var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
 06485                && IsOpenclFullSupported()
 06486                && _mediaEncoder.SupportsFilter("alphasrc");
 06487            var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06488                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06489            var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 6490
 06491            if (is8bitSwFormatsAmf)
 6492            {
 06493                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06494                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6495                {
 06496                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6497                }
 6498
 06499                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6500                {
 06501                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6502                }
 6503
 06504                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6505                {
 06506                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6507                }
 6508            }
 6509
 06510            if (is8_10bitSwFormatsAmf)
 6511            {
 06512                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06513                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6514                {
 06515                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6516                }
 6517
 06518                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6519                {
 06520                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6521                }
 6522
 06523                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6524                {
 06525                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6526                }
 6527            }
 6528
 06529            return null;
 6530        }
 6531
 6532        public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6533        {
 06534            if (!OperatingSystem.IsLinux()
 06535                || options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 6536            {
 06537                return null;
 6538            }
 6539
 06540            var hwSurface = IsVaapiSupported(state)
 06541                && IsVaapiFullSupported()
 06542                && IsOpenclFullSupported()
 06543                && _mediaEncoder.SupportsFilter("alphasrc");
 06544            var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06545                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06546            var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06547            var is8_10_12bitSwFormatsVaapi = is8_10bitSwFormatsVaapi
 06548                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06549                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06550                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06551                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06552                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06553                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06554                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6555
 06556            if (is8bitSwFormatsVaapi)
 6557            {
 06558                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06559                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6560                {
 06561                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6562                }
 6563
 06564                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6565                {
 06566                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6567                }
 6568
 06569                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6570                {
 06571                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6572                }
 6573
 06574                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6575                {
 06576                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6577                }
 6578            }
 6579
 06580            if (is8_10bitSwFormatsVaapi)
 6581            {
 06582                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6583                {
 06584                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6585                }
 6586
 06587                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6588                {
 06589                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6590                }
 6591            }
 6592
 06593            if (is8_10_12bitSwFormatsVaapi)
 6594            {
 06595                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06596                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6597                {
 06598                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6599                }
 6600            }
 6601
 06602            return null;
 6603        }
 6604
 6605        public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream,
 6606        {
 06607            if (!OperatingSystem.IsMacOS()
 06608                || options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 6609            {
 06610                return null;
 6611            }
 6612
 06613            var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase
 06614                                    || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnore
 06615            var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, String
 06616            var is8_10_12bitSwFormatsVt = is8_10bitSwFormatsVt
 06617                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06618                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06619                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06620                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06621                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06622                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06623                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 06624            var isAv1SupportedSwFormatsVt = is8_10bitSwFormatsVt || string.Equals("yuv420p12le", videoStream.PixelFormat
 6625
 6626            // The related patches make videotoolbox hardware surface working is only available in jellyfin-ffmpeg 7.0.1
 06627            bool useHwSurface = (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface) && IsVideoToolboxFullSupp
 6628
 06629            if (is8bitSwFormatsVt)
 6630            {
 06631                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6632                {
 06633                    return GetHwaccelType(state, options, "vp8", bitDepth, useHwSurface);
 6634                }
 6635            }
 6636
 06637            if (is8_10bitSwFormatsVt)
 6638            {
 06639                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06640                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6641                {
 06642                    return GetHwaccelType(state, options, "h264", bitDepth, useHwSurface);
 6643                }
 6644
 06645                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6646                {
 06647                    return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
 6648                }
 6649            }
 6650
 06651            if (is8_10_12bitSwFormatsVt)
 6652            {
 06653                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06654                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6655                {
 06656                    return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
 6657                }
 6658
 06659                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06660                    && isAv1SupportedSwFormatsVt
 06661                    && _mediaEncoder.IsVideoToolboxAv1DecodeAvailable)
 6662                {
 06663                    return GetHwaccelType(state, options, "av1", bitDepth, useHwSurface);
 6664                }
 6665            }
 6666
 06667            return null;
 6668        }
 6669
 6670        public string GetRkmppVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6671        {
 06672            var isLinux = OperatingSystem.IsLinux();
 6673
 06674            if (!isLinux
 06675                || options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 6676            {
 06677                return null;
 6678            }
 6679
 06680            var inW = state.VideoStream?.Width;
 06681            var inH = state.VideoStream?.Height;
 06682            var reqW = state.BaseRequest.Width;
 06683            var reqH = state.BaseRequest.Height;
 06684            var reqMaxW = state.BaseRequest.MaxWidth;
 06685            var reqMaxH = state.BaseRequest.MaxHeight;
 6686
 6687            // rkrga RGA2e supports range from 1/16 to 16
 06688            if (!IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 16.0f))
 6689            {
 06690                return null;
 6691            }
 6692
 06693            var isRkmppOclSupported = IsRkmppFullSupported() && IsOpenclFullSupported();
 06694            var hwSurface = isRkmppOclSupported
 06695                && _mediaEncoder.SupportsFilter("alphasrc");
 6696
 6697            // rkrga RGA3 supports range from 1/8 to 8
 06698            var isAfbcSupported = hwSurface && IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 6699
 6700            // TODO: add more 8/10bit and 4:2:2 formats for Rkmpp after finishing the ffcheck tool
 06701            var is8bitSwFormatsRkmpp = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06702                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06703            var is10bitSwFormatsRkmpp = string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIg
 06704            var is8_10bitSwFormatsRkmpp = is8bitSwFormatsRkmpp || is10bitSwFormatsRkmpp;
 6705
 6706            // nv15 and nv20 are bit-stream only formats
 06707            if (is10bitSwFormatsRkmpp && !hwSurface)
 6708            {
 06709                return null;
 6710            }
 6711
 06712            if (is8bitSwFormatsRkmpp)
 6713            {
 06714                if (string.Equals(videoStream.Codec, "mpeg1video", StringComparison.OrdinalIgnoreCase))
 6715                {
 06716                    return GetHwaccelType(state, options, "mpeg1video", bitDepth, hwSurface);
 6717                }
 6718
 06719                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6720                {
 06721                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6722                }
 6723
 06724                if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 6725                {
 06726                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
 6727                }
 6728
 06729                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6730                {
 06731                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6732                }
 6733            }
 6734
 06735            if (is8_10bitSwFormatsRkmpp)
 6736            {
 06737                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06738                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6739                {
 06740                    var accelType = GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 06741                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 6742                }
 6743
 06744                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06745                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6746                {
 06747                    var accelType = GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 06748                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 6749                }
 6750
 06751                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6752                {
 06753                    var accelType = GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 06754                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 6755                }
 6756
 06757                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 6758                {
 06759                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6760                }
 6761            }
 6762
 06763            return null;
 6764        }
 6765
 6766        /// <summary>
 6767        /// Gets the number of threads.
 6768        /// </summary>
 6769        /// <param name="state">Encoding state.</param>
 6770        /// <param name="encodingOptions">Encoding options.</param>
 6771        /// <param name="outputVideoCodec">Video codec to use.</param>
 6772        /// <returns>Number of threads.</returns>
 6773#nullable enable
 6774        public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVide
 6775        {
 06776            var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount;
 6777
 06778            if (threads <= 0)
 6779            {
 6780                // Automatically set thread count
 06781                return 0;
 6782            }
 6783
 06784            return Math.Min(threads, Environment.ProcessorCount);
 6785        }
 6786
 6787#nullable disable
 6788        public void TryStreamCopy(EncodingJobInfo state)
 6789        {
 06790            if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream))
 6791            {
 06792                state.OutputVideoCodec = "copy";
 6793            }
 6794            else
 6795            {
 06796                var user = state.User;
 6797
 6798                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 06799                if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
 6800                {
 06801                    state.OutputVideoCodec = "copy";
 6802                }
 6803            }
 6804
 06805            if (state.AudioStream is not null
 06806                && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs))
 6807            {
 06808                state.OutputAudioCodec = "copy";
 6809            }
 6810            else
 6811            {
 06812                var user = state.User;
 6813
 6814                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 06815                if (user is not null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
 6816                {
 06817                    state.OutputAudioCodec = "copy";
 6818                }
 6819            }
 06820        }
 6821
 6822        public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
 6823        {
 06824            var inputModifier = string.Empty;
 06825            var analyzeDurationArgument = string.Empty;
 6826
 6827            // Apply -analyzeduration as per the environment variable,
 6828            // otherwise ffmpeg will break on certain files due to default value is 0.
 06829            var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
 6830
 06831            if (state.MediaSource.AnalyzeDurationMs > 0)
 6832            {
 06833                analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToStr
 6834            }
 06835            else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
 6836            {
 06837                analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
 6838            }
 6839
 06840            if (!string.IsNullOrEmpty(analyzeDurationArgument))
 6841            {
 06842                inputModifier += " " + analyzeDurationArgument;
 6843            }
 6844
 06845            inputModifier = inputModifier.Trim();
 6846
 6847            // Apply -probesize if configured
 06848            var ffmpegProbeSize = _config.GetFFmpegProbeSize();
 6849
 06850            if (!string.IsNullOrEmpty(ffmpegProbeSize))
 6851            {
 06852                inputModifier += $" -probesize {ffmpegProbeSize}";
 6853            }
 6854
 06855            var userAgentParam = GetUserAgentParam(state);
 6856
 06857            if (!string.IsNullOrEmpty(userAgentParam))
 6858            {
 06859                inputModifier += " " + userAgentParam;
 6860            }
 6861
 06862            inputModifier = inputModifier.Trim();
 6863
 06864            var refererParam = GetRefererParam(state);
 6865
 06866            if (!string.IsNullOrEmpty(refererParam))
 6867            {
 06868                inputModifier += " " + refererParam;
 6869            }
 6870
 06871            inputModifier = inputModifier.Trim();
 6872
 06873            inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions, segmentContainer);
 06874            inputModifier = inputModifier.Trim();
 6875
 06876            if (state.InputProtocol == MediaProtocol.Rtsp)
 6877            {
 06878                inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
 6879            }
 6880
 06881            if (!string.IsNullOrEmpty(state.InputAudioSync))
 6882            {
 06883                inputModifier += " -async " + state.InputAudioSync;
 6884            }
 6885
 06886            if (!string.IsNullOrEmpty(state.InputVideoSync))
 6887            {
 06888                inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion);
 6889            }
 6890
 06891            if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp)
 6892            {
 06893                inputModifier += " -re";
 6894            }
 06895            else if (encodingOptions.EnableSegmentDeletion
 06896                && state.VideoStream is not null
 06897                && state.TranscodingType == TranscodingJobType.Hls
 06898                && IsCopyCodec(state.OutputVideoCodec)
 06899                && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateOption)
 6900            {
 6901                // Set an input read rate limit 10x for using SegmentDeletion with stream-copy
 6902                // to prevent ffmpeg from exiting prematurely (due to fast drive)
 06903                inputModifier += " -readrate 10";
 6904            }
 6905
 06906            var flags = new List<string>();
 06907            if (state.IgnoreInputDts)
 6908            {
 06909                flags.Add("+igndts");
 6910            }
 6911
 06912            if (state.IgnoreInputIndex)
 6913            {
 06914                flags.Add("+ignidx");
 6915            }
 6916
 06917            if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
 6918            {
 06919                flags.Add("+genpts");
 6920            }
 6921
 06922            if (state.DiscardCorruptFramesInput)
 6923            {
 06924                flags.Add("+discardcorrupt");
 6925            }
 6926
 06927            if (state.EnableFastSeekInput)
 6928            {
 06929                flags.Add("+fastseek");
 6930            }
 6931
 06932            if (flags.Count > 0)
 6933            {
 06934                inputModifier += " -fflags " + string.Join(string.Empty, flags);
 6935            }
 6936
 06937            if (state.IsVideoRequest)
 6938            {
 06939                if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && encodingOpt
 6940                {
 06941                    var inputFormat = GetInputFormat(state.InputContainer);
 06942                    if (!string.IsNullOrEmpty(inputFormat))
 6943                    {
 06944                        inputModifier += " -f " + inputFormat;
 6945                    }
 6946                }
 6947            }
 6948
 06949            if (state.MediaSource.RequiresLooping)
 6950            {
 06951                inputModifier += " -stream_loop -1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2";
 6952            }
 6953
 06954            return inputModifier;
 6955        }
 6956
 6957        public void AttachMediaSourceInfo(
 6958            EncodingJobInfo state,
 6959            EncodingOptions encodingOptions,
 6960            MediaSourceInfo mediaSource,
 6961            string requestedUrl)
 6962        {
 06963            ArgumentNullException.ThrowIfNull(state);
 6964
 06965            ArgumentNullException.ThrowIfNull(mediaSource);
 6966
 06967            var path = mediaSource.Path;
 06968            var protocol = mediaSource.Protocol;
 6969
 06970            if (!string.IsNullOrEmpty(mediaSource.EncoderPath) && mediaSource.EncoderProtocol.HasValue)
 6971            {
 06972                path = mediaSource.EncoderPath;
 06973                protocol = mediaSource.EncoderProtocol.Value;
 6974            }
 6975
 06976            state.MediaPath = path;
 06977            state.InputProtocol = protocol;
 06978            state.InputContainer = mediaSource.Container;
 06979            state.RunTimeTicks = mediaSource.RunTimeTicks;
 06980            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 6981
 06982            state.IsoType = mediaSource.IsoType;
 6983
 06984            if (mediaSource.Timestamp.HasValue)
 6985            {
 06986                state.InputTimestamp = mediaSource.Timestamp.Value;
 6987            }
 6988
 06989            state.RunTimeTicks = mediaSource.RunTimeTicks;
 06990            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 06991            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
 6992
 06993            if (state.ReadInputAtNativeFramerate
 06994                || (mediaSource.Protocol == MediaProtocol.File
 06995                && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)))
 6996            {
 06997                state.InputVideoSync = "-1";
 06998                state.InputAudioSync = "1";
 6999            }
 7000
 07001            if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase)
 07002                || string.Equals(mediaSource.Container, "asf", StringComparison.OrdinalIgnoreCase))
 7003            {
 7004                // Seeing some stuttering when transcoding wma to audio-only HLS
 07005                state.InputAudioSync = "1";
 7006            }
 7007
 07008            var mediaStreams = mediaSource.MediaStreams;
 7009
 07010            if (state.IsVideoRequest)
 7011            {
 07012                var videoRequest = state.BaseRequest;
 7013
 07014                if (string.IsNullOrEmpty(videoRequest.VideoCodec))
 7015                {
 07016                    if (string.IsNullOrEmpty(requestedUrl))
 7017                    {
 07018                        requestedUrl = "test." + videoRequest.Container;
 7019                    }
 7020
 07021                    videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
 7022                }
 7023
 07024                state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
 07025                state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Su
 07026                state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
 07027                state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
 7028
 07029                if (state.SubtitleStream is not null && !state.SubtitleStream.IsExternal)
 7030                {
 07031                    state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !
 7032                }
 7033
 07034                EnforceResolutionLimit(state);
 7035
 07036                NormalizeSubtitleEmbed(state);
 7037            }
 7038            else
 7039            {
 07040                state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
 7041            }
 7042
 07043            state.MediaSource = mediaSource;
 7044
 07045            var request = state.BaseRequest;
 07046            var supportedAudioCodecs = state.SupportedAudioCodecs;
 07047            if (request is not null && supportedAudioCodecs is not null && supportedAudioCodecs.Length > 0)
 7048            {
 07049                var supportedAudioCodecsList = supportedAudioCodecs.ToList();
 7050
 07051                ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
 7052
 07053                state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray();
 7054
 07055                request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(_mediaEncoder.CanEncodeToAudioCodec)
 07056                    ?? state.SupportedAudioCodecs.FirstOrDefault();
 7057            }
 7058
 07059            var supportedVideoCodecs = state.SupportedVideoCodecs;
 07060            if (request is not null && supportedVideoCodecs is not null && supportedVideoCodecs.Length > 0)
 7061            {
 07062                var supportedVideoCodecsList = supportedVideoCodecs.ToList();
 7063
 07064                ShiftVideoCodecsIfNeeded(supportedVideoCodecsList, encodingOptions);
 7065
 07066                state.SupportedVideoCodecs = supportedVideoCodecsList.ToArray();
 7067
 07068                request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
 7069            }
 07070        }
 7071
 7072        private void ShiftAudioCodecsIfNeeded(List<string> audioCodecs, MediaStream audioStream)
 7073        {
 7074            // No need to shift if there is only one supported audio codec.
 07075            if (audioCodecs.Count < 2)
 7076            {
 07077                return;
 7078            }
 7079
 07080            var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
 07081            var shiftAudioCodecs = new List<string>();
 07082            if (inputChannels >= 6)
 7083            {
 7084                // DTS and TrueHD are not supported by HLS
 7085                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07086                shiftAudioCodecs.Add("dts");
 07087                shiftAudioCodecs.Add("truehd");
 7088            }
 7089            else
 7090            {
 7091                // Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
 7092                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07093                shiftAudioCodecs.Add("ac3");
 07094                shiftAudioCodecs.Add("eac3");
 7095            }
 7096
 07097            if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7098            {
 07099                return;
 7100            }
 7101
 07102            while (shiftAudioCodecs.Contains(audioCodecs[0], StringComparison.OrdinalIgnoreCase))
 7103            {
 07104                var removed = audioCodecs[0];
 07105                audioCodecs.RemoveAt(0);
 07106                audioCodecs.Add(removed);
 7107            }
 07108        }
 7109
 7110        private void ShiftVideoCodecsIfNeeded(List<string> videoCodecs, EncodingOptions encodingOptions)
 7111        {
 7112            // No need to shift if there is only one supported video codec.
 07113            if (videoCodecs.Count < 2)
 7114            {
 07115                return;
 7116            }
 7117
 7118            // Shift codecs to the end of list if it's not allowed.
 07119            var shiftVideoCodecs = new List<string>();
 07120            if (!encodingOptions.AllowHevcEncoding)
 7121            {
 07122                shiftVideoCodecs.Add("hevc");
 07123                shiftVideoCodecs.Add("h265");
 7124            }
 7125
 07126            if (!encodingOptions.AllowAv1Encoding)
 7127            {
 07128                shiftVideoCodecs.Add("av1");
 7129            }
 7130
 07131            if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7132            {
 07133                return;
 7134            }
 7135
 07136            while (shiftVideoCodecs.Contains(videoCodecs[0], StringComparison.OrdinalIgnoreCase))
 7137            {
 07138                var removed = videoCodecs[0];
 07139                videoCodecs.RemoveAt(0);
 07140                videoCodecs.Add(removed);
 7141            }
 07142        }
 7143
 7144        private void NormalizeSubtitleEmbed(EncodingJobInfo state)
 7145        {
 07146            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7147            {
 07148                return;
 7149            }
 7150
 7151            // This is tricky to remux in, after converting to dvdsub it's not positioned correctly
 7152            // Therefore, let's just burn it in
 07153            if (string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 7154            {
 07155                state.SubtitleDeliveryMethod = SubtitleDeliveryMethod.Encode;
 7156            }
 07157        }
 7158
 7159        public string GetSubtitleEmbedArguments(EncodingJobInfo state)
 7160        {
 07161            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7162            {
 07163                return string.Empty;
 7164            }
 7165
 07166            var format = state.SupportedSubtitleCodecs.FirstOrDefault();
 7167            string codec;
 7168
 07169            if (string.IsNullOrEmpty(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.Ordin
 7170            {
 07171                codec = "copy";
 7172            }
 7173            else
 7174            {
 07175                codec = format;
 7176            }
 7177
 07178            return " -codec:s:0 " + codec + " -disposition:s:0 default";
 7179        }
 7180
 7181        public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, Encoder
 7182        {
 7183            // Get the output codec name
 07184            var videoCodec = GetVideoEncoder(state, encodingOptions);
 7185
 07186            var format = string.Empty;
 07187            var keyFrame = string.Empty;
 07188            var outputPath = state.OutputFilePath;
 7189
 07190            if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase)
 07191                && state.BaseRequest.Context == EncodingContext.Streaming)
 7192            {
 7193                // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
 07194                format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov";
 7195            }
 7196
 07197            var threads = GetNumberOfThreads(state, encodingOptions, videoCodec);
 7198
 07199            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7200
 07201            return string.Format(
 07202                CultureInfo.InvariantCulture,
 07203                "{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
 07204                inputModifier,
 07205                GetInputArgument(state, encodingOptions, null),
 07206                keyFrame,
 07207                GetMapArgs(state),
 07208                GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset),
 07209                threads,
 07210                GetProgressiveVideoAudioArguments(state, encodingOptions),
 07211                GetSubtitleEmbedArguments(state),
 07212                format,
 07213                outputPath).Trim();
 7214        }
 7215
 7216        public string GetOutputFFlags(EncodingJobInfo state)
 7217        {
 07218            var flags = new List<string>();
 07219            if (state.GenPtsOutput)
 7220            {
 07221                flags.Add("+genpts");
 7222            }
 7223
 07224            if (flags.Count > 0)
 7225            {
 07226                return " -fflags " + string.Join(string.Empty, flags);
 7227            }
 7228
 07229            return string.Empty;
 7230        }
 7231
 7232        public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoC
 7233        {
 07234            var args = "-codec:v:0 " + videoCodec;
 7235
 07236            if (state.BaseRequest.EnableMpegtsM2TsMode)
 7237            {
 07238                args += " -mpegts_m2ts_mode 1";
 7239            }
 7240
 07241            if (IsCopyCodec(videoCodec))
 7242            {
 07243                if (state.VideoStream is not null
 07244                    && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
 07245                    && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
 7246                {
 07247                    string bitStreamArgs = GetBitStreamArgs(state.VideoStream);
 07248                    if (!string.IsNullOrEmpty(bitStreamArgs))
 7249                    {
 07250                        args += " " + bitStreamArgs;
 7251                    }
 7252                }
 7253
 07254                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7255                {
 07256                    args += " -copyts -avoid_negative_ts disabled -start_at_zero";
 7257                }
 7258
 07259                if (!state.RunTimeTicks.HasValue)
 7260                {
 07261                    args += " -fflags +genpts";
 7262                }
 7263            }
 7264            else
 7265            {
 07266                var keyFrameArg = string.Format(
 07267                    CultureInfo.InvariantCulture,
 07268                    " -force_key_frames \"expr:gte(t,n_forced*{0})\"",
 07269                    5);
 7270
 07271                args += keyFrameArg;
 7272
 07273                var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream &&
 7274
 07275                var hasCopyTs = false;
 7276
 7277                // video processing filters.
 07278                var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
 7279
 07280                var negativeMapArgs = GetNegativeMapArgsByFilters(state, videoProcessParam);
 7281
 07282                args = negativeMapArgs + args + videoProcessParam;
 7283
 07284                hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
 7285
 07286                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7287                {
 07288                    if (!hasCopyTs)
 7289                    {
 07290                        args += " -copyts";
 7291                    }
 7292
 07293                    args += " -avoid_negative_ts disabled";
 7294
 07295                    if (!(state.SubtitleStream is not null && state.SubtitleStream.IsExternal && !state.SubtitleStream.I
 7296                    {
 07297                        args += " -start_at_zero";
 7298                    }
 7299                }
 7300
 07301                var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset);
 7302
 07303                if (!string.IsNullOrEmpty(qualityParam))
 7304                {
 07305                    args += " " + qualityParam.Trim();
 7306                }
 7307            }
 7308
 07309            if (!string.IsNullOrEmpty(state.OutputVideoSync))
 7310            {
 07311                args += GetVideoSyncOption(state.OutputVideoSync, _mediaEncoder.EncoderVersion);
 7312            }
 7313
 07314            args += GetOutputFFlags(state);
 7315
 07316            return args;
 7317        }
 7318
 7319        public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions)
 7320        {
 7321            // If the video doesn't have an audio stream, return a default.
 07322            if (state.AudioStream is null && state.VideoStream is not null)
 7323            {
 07324                return string.Empty;
 7325            }
 7326
 7327            // Get the output codec name
 07328            var codec = GetAudioEncoder(state);
 7329
 07330            var args = "-codec:a:0 " + codec;
 7331
 07332            if (IsCopyCodec(codec))
 7333            {
 07334                return args;
 7335            }
 7336
 07337            var channels = state.OutputAudioChannels;
 7338
 07339            var useDownMixAlgorithm = state.AudioStream is not null
 07340                                      && DownMixAlgorithmsHelper.AlgorithmFilterStrings.ContainsKey((encodingOptions.Dow
 7341
 07342            if (channels.HasValue && !useDownMixAlgorithm)
 7343            {
 07344                args += " -ac " + channels.Value;
 7345            }
 7346
 07347            var bitrate = state.OutputAudioBitrate;
 07348            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
 7349            {
 07350                var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2);
 07351                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7352                {
 07353                    args += vbrParam;
 7354                }
 7355                else
 7356                {
 07357                    args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
 7358                }
 7359            }
 7360
 07361            if (state.OutputAudioSampleRate.HasValue)
 7362            {
 07363                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
 7364            }
 7365
 07366            args += GetAudioFilterParam(state, encodingOptions);
 7367
 07368            return args;
 7369        }
 7370
 7371        public string GetProgressiveAudioFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string 
 7372        {
 07373            var audioTranscodeParams = new List<string>();
 7374
 07375            var bitrate = state.OutputAudioBitrate;
 07376            var channels = state.OutputAudioChannels;
 07377            var outputCodec = state.OutputAudioCodec;
 7378
 07379            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
 7380            {
 07381                var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2);
 07382                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7383                {
 07384                    audioTranscodeParams.Add(vbrParam);
 7385                }
 7386                else
 7387                {
 07388                    audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
 7389                }
 7390            }
 7391
 07392            if (channels.HasValue)
 7393            {
 07394                audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)
 7395            }
 7396
 07397            if (!string.IsNullOrEmpty(outputCodec))
 7398            {
 07399                audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
 7400            }
 7401
 07402            if (GetAudioEncoder(state).StartsWith("pcm_", StringComparison.Ordinal))
 7403            {
 07404                audioTranscodeParams.Add(string.Concat("-f ", GetAudioEncoder(state).AsSpan(4)));
 07405                audioTranscodeParams.Add("-ar " + state.BaseRequest.AudioBitRate);
 7406            }
 7407
 07408            if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
 7409            {
 7410                // opus only supports specific sampling rates
 07411                var sampleRate = state.OutputAudioSampleRate;
 07412                if (sampleRate.HasValue)
 7413                {
 07414                    var sampleRateValue = sampleRate.Value switch
 07415                    {
 07416                        <= 8000 => 8000,
 07417                        <= 12000 => 12000,
 07418                        <= 16000 => 16000,
 07419                        <= 24000 => 24000,
 07420                        _ => 48000
 07421                    };
 7422
 07423                    audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
 7424                }
 7425            }
 7426
 7427            // Copy the movflags from GetProgressiveVideoFullCommandLine
 7428            // See #9248 and the associated PR for why this is needed
 07429            if (_mp4ContainerNames.Contains(state.OutputContainer))
 7430            {
 07431                audioTranscodeParams.Add("-movflags empty_moov+delay_moov");
 7432            }
 7433
 07434            var threads = GetNumberOfThreads(state, encodingOptions, null);
 7435
 07436            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7437
 07438            return string.Format(
 07439                CultureInfo.InvariantCulture,
 07440                "{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"",
 07441                inputModifier,
 07442                GetInputArgument(state, encodingOptions, null),
 07443                threads,
 07444                " -vn",
 07445                string.Join(' ', audioTranscodeParams),
 07446                outputPath,
 07447                string.Empty,
 07448                string.Empty,
 07449                string.Empty).Trim();
 7450        }
 7451
 7452        public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
 7453        {
 07454            var index = 0;
 07455            var length = mediaStreams.Count;
 7456
 07457            for (var i = 0; i < length; i++)
 7458            {
 07459                var currentMediaStream = mediaStreams[i];
 07460                if (currentMediaStream == streamToFind)
 7461                {
 07462                    return index;
 7463                }
 7464
 07465                if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
 7466                {
 07467                    index++;
 7468                }
 7469            }
 7470
 07471            return -1;
 7472        }
 7473
 7474        public static bool IsCopyCodec(string codec)
 7475        {
 07476            return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
 7477        }
 7478
 7479        private static bool ShouldEncodeSubtitle(EncodingJobInfo state)
 7480        {
 07481            return state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
 07482                   || (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec));
 7483        }
 7484
 7485        public static string GetVideoSyncOption(string videoSync, Version encoderVersion)
 7486        {
 07487            if (string.IsNullOrEmpty(videoSync))
 7488            {
 07489                return string.Empty;
 7490            }
 7491
 07492            if (encoderVersion >= new Version(5, 1))
 7493            {
 07494                if (int.TryParse(videoSync, CultureInfo.InvariantCulture, out var vsync))
 7495                {
 07496                    return vsync switch
 07497                    {
 07498                        -1 => " -fps_mode auto",
 07499                        0 => " -fps_mode passthrough",
 07500                        1 => " -fps_mode cfr",
 07501                        2 => " -fps_mode vfr",
 07502                        _ => string.Empty
 07503                    };
 7504                }
 7505
 07506                return string.Empty;
 7507            }
 7508
 7509            // -vsync is deprecated in FFmpeg 5.1 and will be removed in the future.
 07510            return $" -vsync {videoSync}";
 7511        }
 7512    }
 7513}

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)
IsVideoStreamHevcRext(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetVideoEncoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetUserAgentParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetRefererParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetInputFormat(System.String)
GetDecoderFromCodec(System.String)
InferAudioCodec(System.String)
InferVideoCodec(System.String)
GetVideoProfileScore(System.String,System.String)
GetAudioEncoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetRkmppDeviceArgs(System.String)
GetVideoToolboxDeviceArgs(System.String)
GetCudaDeviceArgs(System.Int32,System.String)
GetVulkanDeviceArgs(System.Int32,System.String,System.String,System.String)
GetOpenclDeviceArgs(System.Int32,System.String,System.String,System.String)
GetD3d11vaDeviceArgs(System.Int32,System.String,System.String)
GetVaapiDeviceArgs(System.String,System.String,System.String,System.String,System.String,System.String)
GetDrmDeviceArgs(System.String,System.String)
GetQsvDeviceArgs(System.String,System.String)
GetFilterHwDeviceArgs(System.String)
GetGraphicalSubCanvasSize(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetInputVideoHwaccelArgs(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetInputArgument(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
IsH264(MediaBrowser.Model.Entities.MediaStream)
IsH265(MediaBrowser.Model.Entities.MediaStream)
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,System.Boolean)
GetLibplaceboFilter(MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.Boolean,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Boolean)
GetVideoTransposeDirection(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetSwVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetNvidiaVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetNvidiaVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetAmdVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetAmdDx11VidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetIntelVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetIntelQsvDx11VidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetIntelQsvVaapiVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetVaapiVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetIntelVaapiFullVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetAmdVaapiFullVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetVaapiLimitedVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetAppleVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetAppleVidFiltersPreferred(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetRkmppVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetRkmppVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetVideoProcessingFilterParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetOverwriteColorPropertiesParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.Boolean)
GetInputHdrParam(System.String)
GetOutputSdrParam(System.String)
GetVideoColorBitDepth(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetHardwareVideoDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetHwDecoderName(MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String,System.String,System.Int32)
GetHwaccelType(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.Int32,System.Boolean)
GetQsvHwVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetNvdecVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetAmfVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetVaapiVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetVideotoolboxVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetRkmppVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetNumberOfThreads(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
TryStreamCopy(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetInputModifier(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
AttachMediaSourceInfo(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Dto.MediaSourceInfo,System.String)
ShiftAudioCodecsIfNeeded(System.Collections.Generic.List`1<System.String>,MediaBrowser.Model.Entities.MediaStream)
ShiftVideoCodecsIfNeeded(System.Collections.Generic.List`1<System.String>,MediaBrowser.Model.Configuration.EncodingOptions)
NormalizeSubtitleEmbed(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetSubtitleEmbedArguments(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetProgressiveVideoFullCommandLine(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.EncoderPreset)
GetOutputFFlags(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetProgressiveVideoArguments(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,MediaBrowser.Model.Entities.EncoderPreset)
GetProgressiveVideoAudioArguments(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetProgressiveAudioFullCommandLine(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
FindIndex(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Model.Entities.MediaStream>,MediaBrowser.Model.Entities.MediaStream)
IsCopyCodec(System.String)
ShouldEncodeSubtitle(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetVideoSyncOption(System.String,System.Version)