< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.MediaEncoding.EncodingHelper
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
Line coverage
0%
Covered lines: 26
Uncovered lines: 3674
Coverable lines: 3700
Total lines: 7733
Line coverage: 0.7%
Branch coverage
0%
Covered branches: 0
Total branches: 3673
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.cctor()100%210%
GetH264Encoder(...)100%210%
GetH265Encoder(...)100%210%
GetAv1Encoder(...)100%210%
GetH26xOrAv1Encoder(...)0%110100%
GetMjpegEncoder(...)0%272160%
IsVaapiSupported(...)0%2040%
IsVaapiFullSupported()0%272160%
IsRkmppFullSupported()0%4260%
IsOpenclFullSupported()0%4260%
IsCudaFullSupported()0%110100%
IsVulkanFullSupported()0%110100%
IsVideoToolboxFullSupported()0%7280%
IsSwTonemapAvailable(...)0%4260%
IsHwTonemapAvailable(...)0%420200%
IsVulkanHwTonemapAvailable(...)0%4260%
IsIntelVppTonemapAvailable(...)0%210140%
IsVideoToolboxTonemapAvailable(...)0%110100%
IsVideoStreamHevcRext(...)0%342180%
GetVideoEncoder(...)0%210140%
GetUserAgentParam(...)0%620%
GetRefererParam(...)0%620%
GetInputFormat(...)0%1482380%
GetDecoderFromCodec(...)0%7280%
InferAudioCodec(...)0%3422580%
InferVideoCodec(...)0%156120%
GetVideoProfileScore(...)0%4260%
GetAudioEncoder(...)0%420200%
GetRkmppDeviceArgs(...)0%620%
GetVideoToolboxDeviceArgs(...)0%620%
GetCudaDeviceArgs(...)0%2040%
GetVulkanDeviceArgs(...)0%7280%
GetOpenclDeviceArgs(...)0%7280%
GetD3d11vaDeviceArgs(...)0%4260%
GetVaapiDeviceArgs(...)0%272160%
GetDrmDeviceArgs(...)0%2040%
GetQsvDeviceArgs(...)0%7280%
GetFilterHwDeviceArgs(...)0%620%
GetGraphicalSubCanvasSize(...)0%420200%
GetInputVideoHwaccelArgs(...)0%145201200%
GetInputArgument(...)0%1190340%
IsH264(...)0%2040%
IsH265(...)0%2040%
IsAv1(...)0%620%
IsAAC(...)0%620%
IsDoviWithHdr10Bl(...)0%7280%
IsDovi(...)0%110100%
IsHdr10Plus(...)0%7280%
ShouldRemoveDynamicHdrMetadata(...)0%600240%
CanEncoderRemoveDynamicHdrMetadata(...)0%342180%
IsDoviRemoved(...)0%4260%
IsHdr10PlusRemoved(...)0%4260%
GetBitStreamArgs(...)0%702260%
GetAudioBitStreamArguments(...)0%110100%
GetSegmentFileExtension(...)0%620%
GetVideoBitrateParam(...)0%1482380%
GetEncoderParam(...)0%8010890%
NormalizeTranscodingLevel(...)0%506220%
GetTextSubtitlesFilter(...)0%210140%
GetFramerateParam(...)0%156120%
GetHlsVideoKeyFrameArguments(...)0%1806420%
GetVideoQualityParam(...)0%325801800%
CanStreamCopyVideo(...)0%140421180%
CanStreamCopyAudio(...)0%1190340%
GetVideoBitrateParamValue(...)0%506220%
GetMinBitrate(...)0%2040%
GetVideoBitrateScaleFactor(...)0%7280%
ScaleBitrate(...)0%110100%
GetAudioBitrateParam(...)100%210%
GetAudioBitrateParam(...)0%2162460%
GetAudioVbrModeParam(...)0%1190340%
GetAudioFilterParam(...)0%702260%
GetNumAudioChannelsParam(...)0%930300%
EnforceResolutionLimit(...)0%2040%
GetFastSeekCommandLineParameter(...)0%506220%
GetMapArgs(...)0%1640400%
GetNegativeMapArgsByFilters(...)0%2040%
GetMediaStream(...)0%156120%
GetFixedOutputSize(...)0%272160%
IsScaleRatioSupported(...)0%506220%
GetHwScaleFilter(...)0%600240%
GetGraphicalSubPreProcessFilters(...)0%342180%
GetAlphaSrcFilter(...)0%4260%
GetSwScaleFilter(...)0%702260%
GetFixedSwScaleFilter(...)0%132110%
GetSwDeinterlaceFilter(...)0%4260%
GetHwDeinterlaceFilter(...)0%702260%
GetHwTonemapFilter(...)0%1332360%
GetLibplaceboFilter(...)0%506220%
GetVideoTransposeDirection(...)0%156120%
GetSwVidFilterChain(...)0%2550500%
GetNvidiaVidFilterChain(...)0%110100%
GetNvidiaVidFiltersPrefered(...)0%7832880%
GetAmdVidFilterChain(...)0%210140%
GetAmdDx11VidFiltersPrefered(...)0%7482860%
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%2970540%
GetNumberOfThreads(...)0%4260%
TryStreamCopy(...)0%272160%
GetInputModifier(...)0%3660600%
AttachMediaSourceInfo(...)0%1806420%
ShiftAudioCodecsIfNeeded(...)0%110100%
ShiftVideoCodecsIfNeeded(...)0%110100%
NormalizeSubtitleEmbed(...)0%4260%
GetSubtitleEmbedArguments(...)0%7280%
GetProgressiveVideoFullCommandLine(...)0%2040%
GetOutputFFlags(...)0%2040%
GetProgressiveVideoArguments(...)0%1482380%
GetProgressiveVideoAudioArguments(...)0%600240%
GetProgressiveAudioFullCommandLine(...)0%930300%
FindIndex(...)0%4260%
IsCopyCodec(...)100%210%
ShouldEncodeSubtitle(...)0%2040%
GetVideoSyncOption(...)0%132110%

File(s)

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

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CS1591
 4// We need lowercase normalized string for ffmpeg
 5#pragma warning disable CA1308
 6
 7using System;
 8using System.Collections.Generic;
 9using System.Globalization;
 10using System.IO;
 11using System.Linq;
 12using System.Runtime.InteropServices;
 13using System.Text;
 14using System.Text.RegularExpressions;
 15using System.Threading;
 16using Jellyfin.Data;
 17using Jellyfin.Data.Enums;
 18using Jellyfin.Database.Implementations.Enums;
 19using Jellyfin.Extensions;
 20using MediaBrowser.Common.Configuration;
 21using MediaBrowser.Controller.Extensions;
 22using MediaBrowser.Controller.IO;
 23using MediaBrowser.Model.Configuration;
 24using MediaBrowser.Model.Dlna;
 25using MediaBrowser.Model.Dto;
 26using MediaBrowser.Model.Entities;
 27using MediaBrowser.Model.MediaInfo;
 28using Microsoft.Extensions.Configuration;
 29using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
 30
 31namespace MediaBrowser.Controller.MediaEncoding
 32{
 33    public partial class EncodingHelper
 34    {
 35        /// <summary>
 36        /// The codec validation regex.
 37        /// This regular expression matches strings that consist of alphanumeric characters, hyphens,
 38        /// periods, underscores, commas, and vertical bars, with a length between 0 and 40 characters.
 39        /// This should matches all common valid codecs.
 40        /// </summary>
 41        public const string ContainerValidationRegex = @"^[a-zA-Z0-9\-\._,|]{0,40}$";
 42
 43        /// <summary>
 44        /// The level validation regex.
 45        /// This regular expression matches strings representing a double.
 46        /// </summary>
 47        public const string LevelValidationRegex = @"-?[0-9]+(?:\.[0-9]+)?";
 48
 49        private const string _defaultMjpegEncoder = "mjpeg";
 50
 51        private const string QsvAlias = "qs";
 52        private const string VaapiAlias = "va";
 53        private const string D3d11vaAlias = "dx11";
 54        private const string VideotoolboxAlias = "vt";
 55        private const string RkmppAlias = "rk";
 56        private const string OpenclAlias = "ocl";
 57        private const string CudaAlias = "cu";
 58        private const string DrmAlias = "dr";
 59        private const string VulkanAlias = "vk";
 60        private readonly IApplicationPaths _appPaths;
 61        private readonly IMediaEncoder _mediaEncoder;
 62        private readonly ISubtitleEncoder _subtitleEncoder;
 63        private readonly IConfiguration _config;
 64        private readonly IConfigurationManager _configurationManager;
 65        private readonly IPathManager _pathManager;
 66
 67        // i915 hang was fixed by linux 6.2 (3f882f2)
 2168        private readonly Version _minKerneli915Hang = new Version(5, 18);
 2169        private readonly Version _maxKerneli915Hang = new Version(6, 1, 3);
 2170        private readonly Version _minFixedKernel60i915Hang = new Version(6, 0, 18);
 2171        private readonly Version _minKernelVersionAmdVkFmtModifier = new Version(5, 15);
 72
 2173        private readonly Version _minFFmpegImplicitHwaccel = new Version(6, 0);
 2174        private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0);
 2175        private readonly Version _minFFmpegOclCuTonemapMode = new Version(5, 1, 3);
 2176        private readonly Version _minFFmpegSvtAv1Params = new Version(5, 1);
 2177        private readonly Version _minFFmpegVaapiH26xEncA53CcSei = new Version(6, 0);
 2178        private readonly Version _minFFmpegReadrateOption = new Version(5, 0);
 2179        private readonly Version _minFFmpegWorkingVtHwSurface = new Version(7, 0, 1);
 2180        private readonly Version _minFFmpegDisplayRotationOption = new Version(6, 0);
 2181        private readonly Version _minFFmpegAdvancedTonemapMode = new Version(7, 0, 1);
 2182        private readonly Version _minFFmpegAlteredVaVkInterop = new Version(7, 0, 1);
 2183        private readonly Version _minFFmpegQsvVppTonemapOption = new Version(7, 0, 1);
 2184        private readonly Version _minFFmpegQsvVppOutRangeOption = new Version(7, 0, 1);
 2185        private readonly Version _minFFmpegVaapiDeviceVendorId = new Version(7, 0, 1);
 2186        private readonly Version _minFFmpegQsvVppScaleModeOption = new Version(6, 0);
 2187        private readonly Version _minFFmpegRkmppHevcDecDoviRpu = new Version(7, 1, 1);
 88
 089        private static readonly Regex _containerValidationRegex = new(ContainerValidationRegex, RegexOptions.Compiled);
 90
 091        private static readonly string[] _videoProfilesH264 =
 092        [
 093            "ConstrainedBaseline",
 094            "Baseline",
 095            "Extended",
 096            "Main",
 097            "High",
 098            "ProgressiveHigh",
 099            "ConstrainedHigh",
 0100            "High10"
 0101        ];
 102
 0103        private static readonly string[] _videoProfilesH265 =
 0104        [
 0105            "Main",
 0106            "Main10"
 0107        ];
 108
 0109        private static readonly string[] _videoProfilesAv1 =
 0110        [
 0111            "Main",
 0112            "High",
 0113            "Professional",
 0114        ];
 115
 0116        private static readonly HashSet<string> _mp4ContainerNames = new(StringComparer.OrdinalIgnoreCase)
 0117        {
 0118            "mp4",
 0119            "m4a",
 0120            "m4p",
 0121            "m4b",
 0122            "m4r",
 0123            "m4v",
 0124        };
 125
 0126        private static readonly TonemappingMode[] _legacyTonemapModes = [TonemappingMode.max, TonemappingMode.rgb];
 0127        private static readonly TonemappingMode[] _advancedTonemapModes = [TonemappingMode.lum, TonemappingMode.itp];
 128
 129        // Set max transcoding channels for encoders that can't handle more than a set amount of channels
 130        // AAC, FLAC, ALAC, libopus, libvorbis encoders all support at least 8 channels
 0131        private static readonly Dictionary<string, int> _audioTranscodeChannelLookup = new(StringComparer.OrdinalIgnoreC
 0132        {
 0133            { "libmp3lame", 2 },
 0134            { "libfdk_aac", 6 },
 0135            { "ac3", 6 },
 0136            { "eac3", 6 },
 0137            { "dca", 6 },
 0138            { "mlp", 6 },
 0139            { "truehd", 6 },
 0140        };
 141
 0142        private static readonly Dictionary<HardwareAccelerationType, string> _mjpegCodecMap = new()
 0143        {
 0144            { HardwareAccelerationType.vaapi, _defaultMjpegEncoder + "_vaapi" },
 0145            { HardwareAccelerationType.qsv, _defaultMjpegEncoder + "_qsv" },
 0146            { HardwareAccelerationType.videotoolbox, _defaultMjpegEncoder + "_videotoolbox" },
 0147            { HardwareAccelerationType.rkmpp, _defaultMjpegEncoder + "_rkmpp" }
 0148        };
 149
 0150        public static readonly string[] LosslessAudioCodecs =
 0151        [
 0152            "alac",
 0153            "ape",
 0154            "flac",
 0155            "mlp",
 0156            "truehd",
 0157            "wavpack"
 0158        ];
 159
 160        public EncodingHelper(
 161            IApplicationPaths appPaths,
 162            IMediaEncoder mediaEncoder,
 163            ISubtitleEncoder subtitleEncoder,
 164            IConfiguration config,
 165            IConfigurationManager configurationManager,
 166            IPathManager pathManager)
 167        {
 21168            _appPaths = appPaths;
 21169            _mediaEncoder = mediaEncoder;
 21170            _subtitleEncoder = subtitleEncoder;
 21171            _config = config;
 21172            _configurationManager = configurationManager;
 21173            _pathManager = pathManager;
 21174        }
 175
 176        private enum DynamicHdrMetadataRemovalPlan
 177        {
 178            None,
 179            RemoveDovi,
 180            RemoveHdr10Plus,
 181        }
 182
 183        [GeneratedRegex(@"\s+")]
 184        private static partial Regex WhiteSpaceRegex();
 185
 186        public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0187            => GetH26xOrAv1Encoder("libx264", "h264", state, encodingOptions);
 188
 189        public string GetH265Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0190            => GetH26xOrAv1Encoder("libx265", "hevc", state, encodingOptions);
 191
 192        public string GetAv1Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0193            => GetH26xOrAv1Encoder("libsvtav1", "av1", state, encodingOptions);
 194
 195        private string GetH26xOrAv1Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptio
 196        {
 197            // Only use alternative encoders for video files.
 198            // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying
 199            // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such 
 0200            if (state.VideoType == VideoType.VideoFile)
 201            {
 0202                var hwType = encodingOptions.HardwareAccelerationType;
 203
 0204                var codecMap = new Dictionary<HardwareAccelerationType, string>()
 0205                {
 0206                    { HardwareAccelerationType.amf,                  hwEncoder + "_amf" },
 0207                    { HardwareAccelerationType.nvenc,                hwEncoder + "_nvenc" },
 0208                    { HardwareAccelerationType.qsv,                  hwEncoder + "_qsv" },
 0209                    { HardwareAccelerationType.vaapi,                hwEncoder + "_vaapi" },
 0210                    { HardwareAccelerationType.videotoolbox,         hwEncoder + "_videotoolbox" },
 0211                    { HardwareAccelerationType.v4l2m2m,              hwEncoder + "_v4l2m2m" },
 0212                    { HardwareAccelerationType.rkmpp,                hwEncoder + "_rkmpp" },
 0213                };
 214
 0215                if (hwType != HardwareAccelerationType.none
 0216                    && encodingOptions.EnableHardwareEncoding
 0217                    && codecMap.TryGetValue(hwType, out var preferredEncoder)
 0218                    && _mediaEncoder.SupportsEncoder(preferredEncoder))
 219                {
 0220                    return preferredEncoder;
 221                }
 222            }
 223
 0224            return defaultEncoder;
 225        }
 226
 227        private string GetMjpegEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 228        {
 0229            if (state.VideoType == VideoType.VideoFile)
 230            {
 0231                var hwType = encodingOptions.HardwareAccelerationType;
 232
 233                // Only Intel has VA-API MJPEG encoder
 0234                if (hwType == HardwareAccelerationType.vaapi
 0235                    && !(_mediaEncoder.IsVaapiDeviceInteliHD
 0236                         || _mediaEncoder.IsVaapiDeviceInteli965))
 237                {
 0238                    return _defaultMjpegEncoder;
 239                }
 240
 0241                if (hwType != HardwareAccelerationType.none
 0242                    && encodingOptions.EnableHardwareEncoding
 0243                    && _mjpegCodecMap.TryGetValue(hwType, out var preferredEncoder)
 0244                    && _mediaEncoder.SupportsEncoder(preferredEncoder))
 245                {
 0246                    return preferredEncoder;
 247                }
 248            }
 249
 0250            return _defaultMjpegEncoder;
 251        }
 252
 253        private bool IsVaapiSupported(EncodingJobInfo state)
 254        {
 255            // vaapi will throw an error with this input
 256            // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99.
 0257            if (string.Equals(state.VideoStream?.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 258            {
 0259                return false;
 260            }
 261
 0262            return _mediaEncoder.SupportsHwaccel("vaapi");
 263        }
 264
 265        private bool IsVaapiFullSupported()
 266        {
 0267            return _mediaEncoder.SupportsHwaccel("drm")
 0268                   && _mediaEncoder.SupportsHwaccel("vaapi")
 0269                   && _mediaEncoder.SupportsFilter("scale_vaapi")
 0270                   && _mediaEncoder.SupportsFilter("deinterlace_vaapi")
 0271                   && _mediaEncoder.SupportsFilter("tonemap_vaapi")
 0272                   && _mediaEncoder.SupportsFilter("procamp_vaapi")
 0273                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVaapiFrameSync)
 0274                   && _mediaEncoder.SupportsFilter("transpose_vaapi")
 0275                   && _mediaEncoder.SupportsFilter("hwupload_vaapi");
 276        }
 277
 278        private bool IsRkmppFullSupported()
 279        {
 0280            return _mediaEncoder.SupportsHwaccel("rkmpp")
 0281                   && _mediaEncoder.SupportsFilter("scale_rkrga")
 0282                   && _mediaEncoder.SupportsFilter("vpp_rkrga")
 0283                   && _mediaEncoder.SupportsFilter("overlay_rkrga");
 284        }
 285
 286        private bool IsOpenclFullSupported()
 287        {
 0288            return _mediaEncoder.SupportsHwaccel("opencl")
 0289                   && _mediaEncoder.SupportsFilter("scale_opencl")
 0290                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390)
 0291                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclFrameSync);
 292
 293            // Let transpose_opencl optional for the time being.
 294        }
 295
 296        private bool IsCudaFullSupported()
 297        {
 0298            return _mediaEncoder.SupportsHwaccel("cuda")
 0299                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat)
 0300                   && _mediaEncoder.SupportsFilter("yadif_cuda")
 0301                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName)
 0302                   && _mediaEncoder.SupportsFilter("overlay_cuda")
 0303                   && _mediaEncoder.SupportsFilter("hwupload_cuda");
 304
 305            // Let transpose_cuda optional for the time being.
 306        }
 307
 308        private bool IsVulkanFullSupported()
 309        {
 0310            return _mediaEncoder.SupportsHwaccel("vulkan")
 0311                   && _mediaEncoder.SupportsFilter("libplacebo")
 0312                   && _mediaEncoder.SupportsFilter("scale_vulkan")
 0313                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVulkanFrameSync)
 0314                   && _mediaEncoder.SupportsFilter("transpose_vulkan")
 0315                   && _mediaEncoder.SupportsFilter("flip_vulkan");
 316        }
 317
 318        private bool IsVideoToolboxFullSupported()
 319        {
 0320            return _mediaEncoder.SupportsHwaccel("videotoolbox")
 0321                && _mediaEncoder.SupportsFilter("yadif_videotoolbox")
 0322                && _mediaEncoder.SupportsFilter("overlay_videotoolbox")
 0323                && _mediaEncoder.SupportsFilter("tonemap_videotoolbox")
 0324                && _mediaEncoder.SupportsFilter("scale_vt");
 325
 326            // Let transpose_vt optional for the time being.
 327        }
 328
 329        private bool IsSwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 330        {
 0331            if (state.VideoStream is null
 0332                || GetVideoColorBitDepth(state) < 10
 0333                || !_mediaEncoder.SupportsFilter("tonemapx"))
 334            {
 0335                return false;
 336            }
 337
 0338            return state.VideoStream.VideoRange == VideoRange.HDR;
 339        }
 340
 341        private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 342        {
 0343            if (state.VideoStream is null
 0344                || !options.EnableTonemapping
 0345                || GetVideoColorBitDepth(state) < 10)
 346            {
 0347                return false;
 348            }
 349
 0350            if (state.VideoStream.VideoRange == VideoRange.HDR
 0351                && state.VideoStream.VideoRangeType == VideoRangeType.DOVI)
 352            {
 353                // Only native SW decoder, HW accelerator and hevc_rkmpp decoder can parse dovi rpu.
 0354                var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 355
 0356                var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 0357                if (isRkmppDecoder
 0358                    && _mediaEncoder.EncoderVersion >= _minFFmpegRkmppHevcDecDoviRpu
 0359                    && string.Equals(state.VideoStream?.Codec, "hevc", StringComparison.OrdinalIgnoreCase))
 360                {
 0361                    return true;
 362                }
 363
 0364                var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 0365                var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 0366                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 0367                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 0368                var isVideoToolBoxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 0369                return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder || isVideoToolBoxDecoder;
 370            }
 371
 372            // GPU tonemapping supports all HDR RangeTypes
 0373            return state.VideoStream.VideoRange == VideoRange.HDR;
 374        }
 375
 376        private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 377        {
 0378            if (state.VideoStream is null)
 379            {
 0380                return false;
 381            }
 382
 383            // libplacebo has partial Dolby Vision to SDR tonemapping support.
 0384            return options.EnableTonemapping
 0385                   && state.VideoStream.VideoRange == VideoRange.HDR
 0386                   && GetVideoColorBitDepth(state) == 10;
 387        }
 388
 389        private bool IsIntelVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 390        {
 0391            if (state.VideoStream is null
 0392                || !options.EnableVppTonemapping
 0393                || GetVideoColorBitDepth(state) < 10)
 394            {
 0395                return false;
 396            }
 397
 398            // prefer 'tonemap_vaapi' over 'vpp_qsv' on Linux for supporting Gen9/KBLx.
 399            // 'vpp_qsv' requires VPL, which is only supported on Gen12/TGLx and newer.
 0400            if (OperatingSystem.IsWindows()
 0401                && options.HardwareAccelerationType == HardwareAccelerationType.qsv
 0402                && _mediaEncoder.EncoderVersion < _minFFmpegQsvVppTonemapOption)
 403            {
 0404                return false;
 405            }
 406
 0407            return state.VideoStream.VideoRange == VideoRange.HDR
 0408                   && IsDoviWithHdr10Bl(state.VideoStream);
 409        }
 410
 411        private bool IsVideoToolboxTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 412        {
 0413            if (state.VideoStream is null
 0414                || !options.EnableVideoToolboxTonemapping
 0415                || GetVideoColorBitDepth(state) < 10)
 416            {
 0417                return false;
 418            }
 419
 420            // Certain DV profile 5 video works in Safari with direct playing, but the VideoToolBox does not produce cor
 421            // All other HDR formats working.
 0422            return state.VideoStream.VideoRange == VideoRange.HDR
 0423                   && (IsDoviWithHdr10Bl(state.VideoStream)
 0424                       || state.VideoStream.VideoRangeType is VideoRangeType.HLG);
 425        }
 426
 427        private bool IsVideoStreamHevcRext(EncodingJobInfo state)
 428        {
 0429            var videoStream = state.VideoStream;
 0430            if (videoStream is null)
 431            {
 0432                return false;
 433            }
 434
 0435            return string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 0436                   && (string.Equals(videoStream.Profile, "Rext", StringComparison.OrdinalIgnoreCase)
 0437                       || string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 0438                       || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 0439                       || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 0440                       || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 0441                       || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase)
 0442                       || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)
 0443                       || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase));
 444        }
 445
 446        /// <summary>
 447        /// Gets the name of the output video codec.
 448        /// </summary>
 449        /// <param name="state">Encoding state.</param>
 450        /// <param name="encodingOptions">Encoding options.</param>
 451        /// <returns>Encoder string.</returns>
 452        public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 453        {
 0454            var codec = state.OutputVideoCodec;
 455
 0456            if (!string.IsNullOrEmpty(codec))
 457            {
 0458                if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 459                {
 0460                    return GetAv1Encoder(state, encodingOptions);
 461                }
 462
 0463                if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 0464                    || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
 465                {
 0466                    return GetH265Encoder(state, encodingOptions);
 467                }
 468
 0469                if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
 470                {
 0471                    return GetH264Encoder(state, encodingOptions);
 472                }
 473
 0474                if (string.Equals(codec, "mjpeg", StringComparison.OrdinalIgnoreCase))
 475                {
 0476                    return GetMjpegEncoder(state, encodingOptions);
 477                }
 478
 0479                if (_containerValidationRegex.IsMatch(codec))
 480                {
 0481                    return codec.ToLowerInvariant();
 482                }
 483            }
 484
 0485            return "copy";
 486        }
 487
 488        /// <summary>
 489        /// Gets the user agent param.
 490        /// </summary>
 491        /// <param name="state">The state.</param>
 492        /// <returns>System.String.</returns>
 493        public string GetUserAgentParam(EncodingJobInfo state)
 494        {
 0495            if (state.RemoteHttpHeaders.TryGetValue("User-Agent", out string useragent))
 496            {
 0497                return "-user_agent \"" + useragent + "\"";
 498            }
 499
 0500            return string.Empty;
 501        }
 502
 503        /// <summary>
 504        /// Gets the referer param.
 505        /// </summary>
 506        /// <param name="state">The state.</param>
 507        /// <returns>System.String.</returns>
 508        public string GetRefererParam(EncodingJobInfo state)
 509        {
 0510            if (state.RemoteHttpHeaders.TryGetValue("Referer", out string referer))
 511            {
 0512                return "-referer \"" + referer + "\"";
 513            }
 514
 0515            return string.Empty;
 516        }
 517
 518        public static string GetInputFormat(string container)
 519        {
 0520            if (string.IsNullOrEmpty(container) || !_containerValidationRegex.IsMatch(container))
 521            {
 0522                return null;
 523            }
 524
 0525            container = container.Replace("mkv", "matroska", StringComparison.OrdinalIgnoreCase);
 526
 0527            if (string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
 528            {
 0529                return "mpegts";
 530            }
 531
 532            // For these need to find out the ffmpeg names
 0533            if (string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase))
 534            {
 0535                return null;
 536            }
 537
 0538            if (string.Equals(container, "wmv", StringComparison.OrdinalIgnoreCase))
 539            {
 0540                return null;
 541            }
 542
 0543            if (string.Equals(container, "mts", StringComparison.OrdinalIgnoreCase))
 544            {
 0545                return null;
 546            }
 547
 0548            if (string.Equals(container, "vob", StringComparison.OrdinalIgnoreCase))
 549            {
 0550                return null;
 551            }
 552
 0553            if (string.Equals(container, "mpg", StringComparison.OrdinalIgnoreCase))
 554            {
 0555                return null;
 556            }
 557
 0558            if (string.Equals(container, "mpeg", StringComparison.OrdinalIgnoreCase))
 559            {
 0560                return null;
 561            }
 562
 0563            if (string.Equals(container, "rec", StringComparison.OrdinalIgnoreCase))
 564            {
 0565                return null;
 566            }
 567
 0568            if (string.Equals(container, "dvr-ms", StringComparison.OrdinalIgnoreCase))
 569            {
 0570                return null;
 571            }
 572
 0573            if (string.Equals(container, "ogm", StringComparison.OrdinalIgnoreCase))
 574            {
 0575                return null;
 576            }
 577
 0578            if (string.Equals(container, "divx", StringComparison.OrdinalIgnoreCase))
 579            {
 0580                return null;
 581            }
 582
 0583            if (string.Equals(container, "tp", StringComparison.OrdinalIgnoreCase))
 584            {
 0585                return null;
 586            }
 587
 0588            if (string.Equals(container, "rmvb", StringComparison.OrdinalIgnoreCase))
 589            {
 0590                return null;
 591            }
 592
 0593            if (string.Equals(container, "rtp", StringComparison.OrdinalIgnoreCase))
 594            {
 0595                return null;
 596            }
 597
 598            // Seeing reported failures here, not sure yet if this is related to specifying input format
 0599            if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase))
 600            {
 0601                return null;
 602            }
 603
 604            // obviously don't do this for strm files
 0605            if (string.Equals(container, "strm", StringComparison.OrdinalIgnoreCase))
 606            {
 0607                return null;
 608            }
 609
 610            // ISO files don't have an ffmpeg format
 0611            if (string.Equals(container, "iso", StringComparison.OrdinalIgnoreCase))
 612            {
 0613                return null;
 614            }
 615
 0616            return container;
 617        }
 618
 619        /// <summary>
 620        /// Gets decoder from a codec.
 621        /// </summary>
 622        /// <param name="codec">Codec to use.</param>
 623        /// <returns>Decoder string.</returns>
 624        public string GetDecoderFromCodec(string codec)
 625        {
 626            // For these need to find out the ffmpeg names
 0627            if (string.Equals(codec, "mp2", StringComparison.OrdinalIgnoreCase))
 628            {
 0629                return null;
 630            }
 631
 0632            if (string.Equals(codec, "aac_latm", StringComparison.OrdinalIgnoreCase))
 633            {
 0634                return null;
 635            }
 636
 0637            if (string.Equals(codec, "eac3", StringComparison.OrdinalIgnoreCase))
 638            {
 0639                return null;
 640            }
 641
 0642            if (_mediaEncoder.SupportsDecoder(codec))
 643            {
 0644                return codec;
 645            }
 646
 0647            return null;
 648        }
 649
 650        /// <summary>
 651        /// Infers the audio codec based on the url.
 652        /// </summary>
 653        /// <param name="container">Container to use.</param>
 654        /// <returns>Codec string.</returns>
 655        public string InferAudioCodec(string container)
 656        {
 0657            if (string.IsNullOrWhiteSpace(container))
 658            {
 659                // this may not work, but if the client is that broken we cannot do anything better
 0660                return "aac";
 661            }
 662
 0663            var inferredCodec = container.ToLowerInvariant();
 664
 0665            return inferredCodec switch
 0666            {
 0667                "ogg" or "oga" or "ogv" or "webm" or "webma" => "opus",
 0668                "m4a" or "m4b" or "mp4" or "mov" or "mkv" or "mka" => "aac",
 0669                "ts" or "avi" or "flv" or "f4v" or "swf" => "mp3",
 0670                _ => inferredCodec
 0671            };
 672        }
 673
 674        /// <summary>
 675        /// Infers the video codec.
 676        /// </summary>
 677        /// <param name="url">The URL.</param>
 678        /// <returns>System.Nullable{VideoCodecs}.</returns>
 679        public string InferVideoCodec(string url)
 680        {
 0681            var ext = Path.GetExtension(url.AsSpan());
 682
 0683            if (ext.Equals(".asf", StringComparison.OrdinalIgnoreCase))
 684            {
 0685                return "wmv";
 686            }
 687
 0688            if (ext.Equals(".webm", StringComparison.OrdinalIgnoreCase))
 689            {
 690                // TODO: this may not always mean VP8, as the codec ages
 0691                return "vp8";
 692            }
 693
 0694            if (ext.Equals(".ogg", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ogv", StringComparison.OrdinalIgn
 695            {
 0696                return "theora";
 697            }
 698
 0699            if (ext.Equals(".m3u8", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ts", StringComparison.OrdinalIgn
 700            {
 0701                return "h264";
 702            }
 703
 0704            return "copy";
 705        }
 706
 707        public int GetVideoProfileScore(string videoCodec, string videoProfile)
 708        {
 709            // strip spaces because they may be stripped out on the query string
 0710            string profile = videoProfile.Replace(" ", string.Empty, StringComparison.Ordinal);
 0711            if (string.Equals("h264", videoCodec, StringComparison.OrdinalIgnoreCase))
 712            {
 0713                return Array.FindIndex(_videoProfilesH264, x => string.Equals(x, profile, StringComparison.OrdinalIgnore
 714            }
 715
 0716            if (string.Equals("hevc", videoCodec, StringComparison.OrdinalIgnoreCase))
 717            {
 0718                return Array.FindIndex(_videoProfilesH265, x => string.Equals(x, profile, StringComparison.OrdinalIgnore
 719            }
 720
 0721            if (string.Equals("av1", videoCodec, StringComparison.OrdinalIgnoreCase))
 722            {
 0723                return Array.FindIndex(_videoProfilesAv1, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreC
 724            }
 725
 0726            return -1;
 727        }
 728
 729        /// <summary>
 730        /// Gets the audio encoder.
 731        /// </summary>
 732        /// <param name="state">The state.</param>
 733        /// <returns>System.String.</returns>
 734        public string GetAudioEncoder(EncodingJobInfo state)
 735        {
 0736            var codec = state.OutputAudioCodec;
 737
 0738            if (!_containerValidationRegex.IsMatch(codec))
 739            {
 0740                codec = "aac";
 741            }
 742
 0743            if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
 744            {
 745                // Use Apple's aac encoder if available as it provides best audio quality
 0746                if (_mediaEncoder.SupportsEncoder("aac_at"))
 747                {
 0748                    return "aac_at";
 749                }
 750
 751                // Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
 0752                if (_mediaEncoder.SupportsEncoder("libfdk_aac"))
 753                {
 0754                    return "libfdk_aac";
 755                }
 756
 0757                return "aac";
 758            }
 759
 0760            if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
 761            {
 0762                return "libmp3lame";
 763            }
 764
 0765            if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase))
 766            {
 0767                return "libvorbis";
 768            }
 769
 0770            if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
 771            {
 0772                return "libopus";
 773            }
 774
 0775            if (string.Equals(codec, "flac", StringComparison.OrdinalIgnoreCase))
 776            {
 0777                return "flac";
 778            }
 779
 0780            if (string.Equals(codec, "dts", StringComparison.OrdinalIgnoreCase))
 781            {
 0782                return "dca";
 783            }
 784
 0785            if (string.Equals(codec, "alac", StringComparison.OrdinalIgnoreCase))
 786            {
 787                // The ffmpeg upstream breaks the AudioToolbox ALAC encoder in version 6.1 but fixes it in version 7.0.
 788                // Since ALAC is lossless in quality and the AudioToolbox encoder is not faster,
 789                // its only benefit is a smaller file size.
 790                // To prevent problems, use the ffmpeg native encoder instead.
 0791                return "alac";
 792            }
 793
 0794            return codec.ToLowerInvariant();
 795        }
 796
 797        private string GetRkmppDeviceArgs(string alias)
 798        {
 0799            alias ??= RkmppAlias;
 800
 801            // device selection in rk is not supported.
 0802            return " -init_hw_device rkmpp=" + alias;
 803        }
 804
 805        private string GetVideoToolboxDeviceArgs(string alias)
 806        {
 0807            alias ??= VideotoolboxAlias;
 808
 809            // device selection in vt is not supported.
 0810            return " -init_hw_device videotoolbox=" + alias;
 811        }
 812
 813        private string GetCudaDeviceArgs(int deviceIndex, string alias)
 814        {
 0815            alias ??= CudaAlias;
 0816            deviceIndex = deviceIndex >= 0
 0817                ? deviceIndex
 0818                : 0;
 819
 0820            return string.Format(
 0821                CultureInfo.InvariantCulture,
 0822                " -init_hw_device cuda={0}:{1}",
 0823                alias,
 0824                deviceIndex);
 825        }
 826
 827        private string GetVulkanDeviceArgs(int deviceIndex, string deviceName, string srcDeviceAlias, string alias)
 828        {
 0829            alias ??= VulkanAlias;
 0830            deviceIndex = deviceIndex >= 0
 0831                ? deviceIndex
 0832                : 0;
 0833            var vendorOpts = string.IsNullOrEmpty(deviceName)
 0834                ? ":" + deviceIndex
 0835                : ":" + "\"" + deviceName + "\"";
 0836            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0837                ? vendorOpts
 0838                : "@" + srcDeviceAlias;
 839
 0840            return string.Format(
 0841                CultureInfo.InvariantCulture,
 0842                " -init_hw_device vulkan={0}{1}",
 0843                alias,
 0844                options);
 845        }
 846
 847        private string GetOpenclDeviceArgs(int deviceIndex, string deviceVendorName, string srcDeviceAlias, string alias
 848        {
 0849            alias ??= OpenclAlias;
 0850            deviceIndex = deviceIndex >= 0
 0851                ? deviceIndex
 0852                : 0;
 0853            var vendorOpts = string.IsNullOrEmpty(deviceVendorName)
 0854                ? ":0.0"
 0855                : ":." + deviceIndex + ",device_vendor=\"" + deviceVendorName + "\"";
 0856            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0857                ? vendorOpts
 0858                : "@" + srcDeviceAlias;
 859
 0860            return string.Format(
 0861                CultureInfo.InvariantCulture,
 0862                " -init_hw_device opencl={0}{1}",
 0863                alias,
 0864                options);
 865        }
 866
 867        private string GetD3d11vaDeviceArgs(int deviceIndex, string deviceVendorId, string alias)
 868        {
 0869            alias ??= D3d11vaAlias;
 0870            deviceIndex = deviceIndex >= 0 ? deviceIndex : 0;
 0871            var options = string.IsNullOrEmpty(deviceVendorId)
 0872                ? deviceIndex.ToString(CultureInfo.InvariantCulture)
 0873                : ",vendor=" + deviceVendorId;
 874
 0875            return string.Format(
 0876                CultureInfo.InvariantCulture,
 0877                " -init_hw_device d3d11va={0}:{1}",
 0878                alias,
 0879                options);
 880        }
 881
 882        private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string vendorId, st
 883        {
 0884            alias ??= VaapiAlias;
 0885            var haveVendorId = !string.IsNullOrEmpty(vendorId)
 0886                && _mediaEncoder.EncoderVersion >= _minFFmpegVaapiDeviceVendorId;
 887
 888            // Priority: 'renderNodePath' > 'vendorId' > 'kernelDriver'
 0889            var driverOpts = File.Exists(renderNodePath)
 0890                ? renderNodePath
 0891                : (haveVendorId ? $",vendor_id={vendorId}" : (string.IsNullOrEmpty(kernelDriver) ? string.Empty : $",ker
 892
 893            // 'driver' behaves similarly to env LIBVA_DRIVER_NAME
 0894            driverOpts += string.IsNullOrEmpty(driver) ? string.Empty : ",driver=" + driver;
 895
 0896            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0897                ? (string.IsNullOrEmpty(driverOpts) ? string.Empty : ":" + driverOpts)
 0898                : "@" + srcDeviceAlias;
 899
 0900            return string.Format(
 0901                CultureInfo.InvariantCulture,
 0902                " -init_hw_device vaapi={0}{1}",
 0903                alias,
 0904                options);
 905        }
 906
 907        private string GetDrmDeviceArgs(string renderNodePath, string alias)
 908        {
 0909            alias ??= DrmAlias;
 0910            renderNodePath = renderNodePath ?? "/dev/dri/renderD128";
 911
 0912            return string.Format(
 0913                CultureInfo.InvariantCulture,
 0914                " -init_hw_device drm={0}:{1}",
 0915                alias,
 0916                renderNodePath);
 917        }
 918
 919        private string GetQsvDeviceArgs(string renderNodePath, string alias)
 920        {
 0921            var arg = " -init_hw_device qsv=" + (alias ?? QsvAlias);
 0922            if (OperatingSystem.IsLinux())
 923            {
 924                // derive qsv from vaapi device
 0925                return GetVaapiDeviceArgs(renderNodePath, "iHD", "i915", "0x8086", null, VaapiAlias) + arg + "@" + Vaapi
 926            }
 927
 0928            if (OperatingSystem.IsWindows())
 929            {
 930                // on Windows, the deviceIndex is an int
 0931                if (int.TryParse(renderNodePath, NumberStyles.Integer, CultureInfo.InvariantCulture, out int deviceIndex
 932                {
 0933                    return GetD3d11vaDeviceArgs(deviceIndex, string.Empty, D3d11vaAlias) + arg + "@" + D3d11vaAlias;
 934                }
 935
 936                // derive qsv from d3d11va device
 0937                return GetD3d11vaDeviceArgs(0, "0x8086", D3d11vaAlias) + arg + "@" + D3d11vaAlias;
 938            }
 939
 0940            return null;
 941        }
 942
 943        private string GetFilterHwDeviceArgs(string alias)
 944        {
 0945            return string.IsNullOrEmpty(alias)
 0946                ? string.Empty
 0947                : " -filter_hw_device " + alias;
 948        }
 949
 950        public string GetGraphicalSubCanvasSize(EncodingJobInfo state)
 951        {
 952            // DVBSUB uses the fixed canvas size 720x576
 0953            if (state.SubtitleStream is not null
 0954                && ShouldEncodeSubtitle(state)
 0955                && !state.SubtitleStream.IsTextSubtitleStream
 0956                && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 957            {
 0958                var subtitleWidth = state.SubtitleStream?.Width;
 0959                var subtitleHeight = state.SubtitleStream?.Height;
 960
 0961                if (subtitleWidth.HasValue
 0962                    && subtitleHeight.HasValue
 0963                    && subtitleWidth.Value > 0
 0964                    && subtitleHeight.Value > 0)
 965                {
 0966                    return string.Format(
 0967                        CultureInfo.InvariantCulture,
 0968                        " -canvas_size {0}x{1}",
 0969                        subtitleWidth.Value,
 0970                        subtitleHeight.Value);
 971                }
 972            }
 973
 0974            return string.Empty;
 975        }
 976
 977        /// <summary>
 978        /// Gets the input video hwaccel argument.
 979        /// </summary>
 980        /// <param name="state">Encoding state.</param>
 981        /// <param name="options">Encoding options.</param>
 982        /// <returns>Input video hwaccel arguments.</returns>
 983        public string GetInputVideoHwaccelArgs(EncodingJobInfo state, EncodingOptions options)
 984        {
 0985            if (!state.IsVideoRequest)
 986            {
 0987                return string.Empty;
 988            }
 989
 0990            var vidEncoder = GetVideoEncoder(state, options) ?? string.Empty;
 0991            if (IsCopyCodec(vidEncoder))
 992            {
 0993                return string.Empty;
 994            }
 995
 0996            var args = new StringBuilder();
 0997            var isWindows = OperatingSystem.IsWindows();
 0998            var isLinux = OperatingSystem.IsLinux();
 0999            var isMacOS = OperatingSystem.IsMacOS();
 01000            var optHwaccelType = options.HardwareAccelerationType;
 01001            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 01002            var isHwTonemapAvailable = IsHwTonemapAvailable(state, options);
 1003
 01004            if (optHwaccelType == HardwareAccelerationType.vaapi)
 1005            {
 01006                if (!isLinux || !_mediaEncoder.SupportsHwaccel("vaapi"))
 1007                {
 01008                    return string.Empty;
 1009                }
 1010
 01011                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01012                var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01013                if (!isVaapiDecoder && !isVaapiEncoder)
 1014                {
 01015                    return string.Empty;
 1016                }
 1017
 01018                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 1019                {
 01020                    args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "iHD", null, null, null, VaapiAlias));
 1021                }
 01022                else if (_mediaEncoder.IsVaapiDeviceInteli965)
 1023                {
 1024                    // Only override i965 since it has lower priority than iHD in libva lookup.
 01025                    Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME", "i965");
 01026                    Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME_JELLYFIN", "i965");
 01027                    args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "i965", null, null, null, VaapiAlias));
 1028                }
 1029
 01030                var filterDevArgs = string.Empty;
 01031                var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
 1032
 01033                if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 1034                {
 01035                    if (doOclTonemap && !isVaapiDecoder)
 1036                    {
 01037                        args.Append(GetOpenclDeviceArgs(0, null, VaapiAlias, OpenclAlias));
 01038                        filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1039                    }
 1040                }
 01041                else if (_mediaEncoder.IsVaapiDeviceAmd)
 1042                {
 1043                    // Disable AMD EFC feature since it's still unstable in upstream Mesa.
 01044                    Environment.SetEnvironmentVariable("AMD_DEBUG", "noefc");
 1045
 01046                    if (IsVulkanFullSupported()
 01047                        && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 01048                        && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 1049                    {
 01050                        args.Append(GetDrmDeviceArgs(options.VaapiDevice, DrmAlias));
 01051                        args.Append(GetVaapiDeviceArgs(null, null, null, null, DrmAlias, VaapiAlias));
 01052                        args.Append(GetVulkanDeviceArgs(0, null, DrmAlias, VulkanAlias));
 1053
 1054                        // libplacebo wants an explicitly set vulkan filter device.
 01055                        filterDevArgs = GetFilterHwDeviceArgs(VulkanAlias);
 1056                    }
 1057                    else
 1058                    {
 01059                        args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, null, null, VaapiAlias));
 01060                        filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias);
 1061
 01062                        if (doOclTonemap)
 1063                        {
 1064                            // ROCm/ROCr OpenCL runtime
 01065                            args.Append(GetOpenclDeviceArgs(0, "Advanced Micro Devices", null, OpenclAlias));
 01066                            filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1067                        }
 1068                    }
 1069                }
 01070                else if (doOclTonemap)
 1071                {
 01072                    args.Append(GetOpenclDeviceArgs(0, null, null, OpenclAlias));
 01073                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1074                }
 1075
 01076                args.Append(filterDevArgs);
 1077            }
 01078            else if (optHwaccelType == HardwareAccelerationType.qsv)
 1079            {
 01080                if ((!isLinux && !isWindows) || !_mediaEncoder.SupportsHwaccel("qsv"))
 1081                {
 01082                    return string.Empty;
 1083                }
 1084
 01085                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 01086                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01087                var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 01088                var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 01089                var isHwDecoder = isQsvDecoder || isVaapiDecoder || isD3d11vaDecoder;
 01090                if (!isHwDecoder && !isQsvEncoder)
 1091                {
 01092                    return string.Empty;
 1093                }
 1094
 01095                args.Append(GetQsvDeviceArgs(options.QsvDevice, QsvAlias));
 01096                var filterDevArgs = GetFilterHwDeviceArgs(QsvAlias);
 1097                // child device used by qsv.
 01098                if (_mediaEncoder.SupportsHwaccel("vaapi") || _mediaEncoder.SupportsHwaccel("d3d11va"))
 1099                {
 01100                    if (isHwTonemapAvailable && IsOpenclFullSupported())
 1101                    {
 01102                        var srcAlias = isLinux ? VaapiAlias : D3d11vaAlias;
 01103                        args.Append(GetOpenclDeviceArgs(0, null, srcAlias, OpenclAlias));
 01104                        if (!isHwDecoder)
 1105                        {
 01106                            filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1107                        }
 1108                    }
 1109                }
 1110
 01111                args.Append(filterDevArgs);
 1112            }
 01113            else if (optHwaccelType == HardwareAccelerationType.nvenc)
 1114            {
 01115                if ((!isLinux && !isWindows) || !IsCudaFullSupported())
 1116                {
 01117                    return string.Empty;
 1118                }
 1119
 01120                var isCuvidDecoder = vidDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase);
 01121                var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 01122                var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 01123                var isHwDecoder = isNvdecDecoder || isCuvidDecoder;
 01124                if (!isHwDecoder && !isNvencEncoder)
 1125                {
 01126                    return string.Empty;
 1127                }
 1128
 01129                args.Append(GetCudaDeviceArgs(0, CudaAlias))
 01130                     .Append(GetFilterHwDeviceArgs(CudaAlias));
 1131            }
 01132            else if (optHwaccelType == HardwareAccelerationType.amf)
 1133            {
 01134                if (!isWindows || !_mediaEncoder.SupportsHwaccel("d3d11va"))
 1135                {
 01136                    return string.Empty;
 1137                }
 1138
 01139                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 01140                var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 01141                if (!isD3d11vaDecoder && !isAmfEncoder)
 1142                {
 01143                    return string.Empty;
 1144                }
 1145
 1146                // no dxva video processor hw filter.
 01147                args.Append(GetD3d11vaDeviceArgs(0, "0x1002", D3d11vaAlias));
 01148                var filterDevArgs = string.Empty;
 01149                if (IsOpenclFullSupported())
 1150                {
 01151                    args.Append(GetOpenclDeviceArgs(0, null, D3d11vaAlias, OpenclAlias));
 01152                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1153                }
 1154
 01155                args.Append(filterDevArgs);
 1156            }
 01157            else if (optHwaccelType == HardwareAccelerationType.videotoolbox)
 1158            {
 01159                if (!isMacOS || !_mediaEncoder.SupportsHwaccel("videotoolbox"))
 1160                {
 01161                    return string.Empty;
 1162                }
 1163
 01164                var isVideotoolboxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 01165                var isVideotoolboxEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 01166                if (!isVideotoolboxDecoder && !isVideotoolboxEncoder)
 1167                {
 01168                    return string.Empty;
 1169                }
 1170
 1171                // videotoolbox hw filter does not require device selection
 01172                args.Append(GetVideoToolboxDeviceArgs(VideotoolboxAlias));
 1173            }
 01174            else if (optHwaccelType == HardwareAccelerationType.rkmpp)
 1175            {
 01176                if (!isLinux || !_mediaEncoder.SupportsHwaccel("rkmpp"))
 1177                {
 01178                    return string.Empty;
 1179                }
 1180
 01181                var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 01182                var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 01183                if (!isRkmppDecoder && !isRkmppEncoder)
 1184                {
 01185                    return string.Empty;
 1186                }
 1187
 01188                args.Append(GetRkmppDeviceArgs(RkmppAlias));
 1189
 01190                var filterDevArgs = string.Empty;
 01191                var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
 1192
 01193                if (doOclTonemap && !isRkmppDecoder)
 1194                {
 01195                    args.Append(GetOpenclDeviceArgs(0, null, RkmppAlias, OpenclAlias));
 01196                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1197                }
 1198
 01199                args.Append(filterDevArgs);
 1200            }
 1201
 01202            if (!string.IsNullOrEmpty(vidDecoder))
 1203            {
 01204                args.Append(vidDecoder);
 1205            }
 1206
 01207            return args.ToString().Trim();
 1208        }
 1209
 1210        /// <summary>
 1211        /// Gets the input argument.
 1212        /// </summary>
 1213        /// <param name="state">Encoding state.</param>
 1214        /// <param name="options">Encoding options.</param>
 1215        /// <param name="segmentContainer">Segment Container.</param>
 1216        /// <returns>Input arguments.</returns>
 1217        public string GetInputArgument(EncodingJobInfo state, EncodingOptions options, string segmentContainer)
 1218        {
 01219            var arg = new StringBuilder();
 01220            var inputVidHwaccelArgs = GetInputVideoHwaccelArgs(state, options);
 1221
 01222            if (!string.IsNullOrEmpty(inputVidHwaccelArgs))
 1223            {
 01224                arg.Append(inputVidHwaccelArgs);
 1225            }
 1226
 01227            var canvasArgs = GetGraphicalSubCanvasSize(state);
 01228            if (!string.IsNullOrEmpty(canvasArgs))
 1229            {
 01230                arg.Append(canvasArgs);
 1231            }
 1232
 01233            if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
 1234            {
 01235                var concatFilePath = Path.Join(_configurationManager.CommonApplicationPaths.CachePath, "concat", state.M
 01236                if (!File.Exists(concatFilePath))
 1237                {
 01238                    _mediaEncoder.GenerateConcatConfig(state.MediaSource, concatFilePath);
 1239                }
 1240
 01241                arg.Append(" -f concat -safe 0 -i \"")
 01242                    .Append(concatFilePath)
 01243                    .Append("\" ");
 1244            }
 1245            else
 1246            {
 01247                arg.Append(" -i ")
 01248                    .Append(_mediaEncoder.GetInputPathArgument(state));
 1249            }
 1250
 1251            // sub2video for external graphical subtitles
 01252            if (state.SubtitleStream is not null
 01253                && ShouldEncodeSubtitle(state)
 01254                && !state.SubtitleStream.IsTextSubtitleStream
 01255                && state.SubtitleStream.IsExternal)
 1256            {
 01257                var subtitlePath = state.SubtitleStream.Path;
 01258                var subtitleExtension = Path.GetExtension(subtitlePath.AsSpan());
 1259
 1260                // dvdsub/vobsub graphical subtitles use .sub+.idx pairs
 01261                if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase))
 1262                {
 01263                    var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
 01264                    if (File.Exists(idxFile))
 1265                    {
 01266                        subtitlePath = idxFile;
 1267                    }
 1268                }
 1269
 1270                // Also seek the external subtitles stream.
 01271                var seekSubParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01272                if (!string.IsNullOrEmpty(seekSubParam))
 1273                {
 01274                    arg.Append(' ').Append(seekSubParam);
 1275                }
 1276
 01277                if (!string.IsNullOrEmpty(canvasArgs))
 1278                {
 01279                    arg.Append(canvasArgs);
 1280                }
 1281
 01282                arg.Append(" -i file:\"").Append(subtitlePath).Append('\"');
 1283            }
 1284
 01285            if (state.AudioStream is not null && state.AudioStream.IsExternal)
 1286            {
 1287                // Also seek the external audio stream.
 01288                var seekAudioParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01289                if (!string.IsNullOrEmpty(seekAudioParam))
 1290                {
 01291                    arg.Append(' ').Append(seekAudioParam);
 1292                }
 1293
 01294                arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
 1295            }
 1296
 1297            // Disable auto inserted SW scaler for HW decoders in case of changed resolution.
 01298            var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
 01299            if (!isSwDecoder)
 1300            {
 01301                arg.Append(" -noautoscale");
 1302            }
 1303
 01304            return arg.ToString();
 1305        }
 1306
 1307        /// <summary>
 1308        /// Determines whether the specified stream is H264.
 1309        /// </summary>
 1310        /// <param name="stream">The stream.</param>
 1311        /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
 1312        public static bool IsH264(MediaStream stream)
 1313        {
 01314            var codec = stream.Codec ?? string.Empty;
 1315
 01316            return codec.Contains("264", StringComparison.OrdinalIgnoreCase)
 01317                    || codec.Contains("avc", StringComparison.OrdinalIgnoreCase);
 1318        }
 1319
 1320        public static bool IsH265(MediaStream stream)
 1321        {
 01322            var codec = stream.Codec ?? string.Empty;
 1323
 01324            return codec.Contains("265", StringComparison.OrdinalIgnoreCase)
 01325                || codec.Contains("hevc", StringComparison.OrdinalIgnoreCase);
 1326        }
 1327
 1328        public static bool IsAv1(MediaStream stream)
 1329        {
 01330            var codec = stream.Codec ?? string.Empty;
 1331
 01332            return codec.Contains("av1", StringComparison.OrdinalIgnoreCase);
 1333        }
 1334
 1335        public static bool IsAAC(MediaStream stream)
 1336        {
 01337            var codec = stream.Codec ?? string.Empty;
 1338
 01339            return codec.Contains("aac", StringComparison.OrdinalIgnoreCase);
 1340        }
 1341
 1342        public static bool IsDoviWithHdr10Bl(MediaStream stream)
 1343        {
 01344            var rangeType = stream?.VideoRangeType;
 1345
 01346            return rangeType is VideoRangeType.DOVIWithHDR10
 01347                or VideoRangeType.DOVIWithEL
 01348                or VideoRangeType.DOVIWithHDR10Plus
 01349                or VideoRangeType.DOVIWithELHDR10Plus
 01350                or VideoRangeType.DOVIInvalid;
 1351        }
 1352
 1353        public static bool IsDovi(MediaStream stream)
 1354        {
 01355            var rangeType = stream?.VideoRangeType;
 1356
 01357            return IsDoviWithHdr10Bl(stream)
 01358                   || (rangeType is VideoRangeType.DOVI
 01359                       or VideoRangeType.DOVIWithHLG
 01360                       or VideoRangeType.DOVIWithSDR);
 1361        }
 1362
 1363        public static bool IsHdr10Plus(MediaStream stream)
 1364        {
 01365            var rangeType = stream?.VideoRangeType;
 1366
 01367            return rangeType is VideoRangeType.HDR10Plus
 01368                       or VideoRangeType.DOVIWithHDR10Plus
 01369                       or VideoRangeType.DOVIWithELHDR10Plus;
 1370        }
 1371
 1372        /// <summary>
 1373        /// Check if dynamic HDR metadata should be removed during stream copy.
 1374        /// Please note this check assumes the range check has already been done
 1375        /// and trivial fallbacks like HDR10+ to HDR10, DOVIWithHDR10 to HDR10 is already checked.
 1376        /// </summary>
 1377        private static DynamicHdrMetadataRemovalPlan ShouldRemoveDynamicHdrMetadata(EncodingJobInfo state)
 1378        {
 01379            var videoStream = state.VideoStream;
 01380            if (videoStream.VideoRange is not VideoRange.HDR)
 1381            {
 01382                return DynamicHdrMetadataRemovalPlan.None;
 1383            }
 1384
 01385            var requestedRangeTypes = state.GetRequestedRangeTypes(state.VideoStream.Codec);
 01386            if (requestedRangeTypes.Length == 0)
 1387            {
 01388                return DynamicHdrMetadataRemovalPlan.None;
 1389            }
 1390
 01391            var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ordinal
 01392            var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIg
 01393            var requestHasDOVIwithEL = requestedRangeTypes.Contains(VideoRangeType.DOVIWithEL.ToString(), StringComparis
 01394            var requestHasDOVIwithELHDR10plus = requestedRangeTypes.Contains(VideoRangeType.DOVIWithELHDR10Plus.ToString
 1395
 01396            var shouldRemoveHdr10Plus = false;
 1397            // Case 1: Client supports HDR10, does not support DOVI with EL but EL presets
 01398            var shouldRemoveDovi = (!requestHasDOVIwithEL && requestHasHDR10) && videoStream.VideoRangeType == VideoRang
 1399
 1400            // Case 2: Client supports DOVI, does not support broken DOVI config
 1401            // Client does not report DOVI support should be allowed to copy bad data for remuxing as HDR10 players woul
 01402            shouldRemoveDovi = shouldRemoveDovi || (requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVII
 1403
 1404            // Special case: we have a video with both EL and HDR10+
 1405            // If the client supports EL but not in the case of coexistence with HDR10+, remove HDR10+ for compatibility
 1406            // Otherwise, remove DOVI if the client is not a DOVI player
 01407            if (videoStream.VideoRangeType == VideoRangeType.DOVIWithELHDR10Plus)
 1408            {
 01409                shouldRemoveHdr10Plus = requestHasDOVIwithEL && !requestHasDOVIwithELHDR10plus;
 01410                shouldRemoveDovi = shouldRemoveDovi || !shouldRemoveHdr10Plus;
 1411            }
 1412
 01413            if (shouldRemoveDovi)
 1414            {
 01415                return DynamicHdrMetadataRemovalPlan.RemoveDovi;
 1416            }
 1417
 1418            // If the client is a Dolby Vision Player, remove the HDR10+ metadata to avoid playback issues
 01419            shouldRemoveHdr10Plus = shouldRemoveHdr10Plus || (requestHasDOVI && videoStream.VideoRangeType == VideoRange
 01420            return shouldRemoveHdr10Plus ? DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus : DynamicHdrMetadataRemovalPlan
 1421        }
 1422
 1423        private bool CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan plan, MediaStream videoStream)
 1424        {
 01425            return plan switch
 01426            {
 01427                DynamicHdrMetadataRemovalPlan.RemoveDovi => _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFil
 01428                                                            || (IsH265(videoStream) && _mediaEncoder.SupportsBitStreamFi
 01429                                                            || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStreamFil
 01430                DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus => (IsH265(videoStream) && _mediaEncoder.SupportsBitStream
 01431                                                                 || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStre
 01432                _ => true,
 01433            };
 1434        }
 1435
 1436        public bool IsDoviRemoved(EncodingJobInfo state)
 1437        {
 01438            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01439                                              && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Remove
 1440        }
 1441
 1442        public bool IsHdr10PlusRemoved(EncodingJobInfo state)
 1443        {
 01444            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01445                                                  && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Re
 1446        }
 1447
 1448        public string GetBitStreamArgs(EncodingJobInfo state, MediaStreamType streamType)
 1449        {
 01450            if (state is null)
 1451            {
 01452                return null;
 1453            }
 1454
 01455            var stream = streamType switch
 01456            {
 01457                MediaStreamType.Audio => state.AudioStream,
 01458                MediaStreamType.Video => state.VideoStream,
 01459                _ => state.VideoStream
 01460            };
 1461            // TODO This is auto inserted into the mpegts mux so it might not be needed.
 1462            // https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
 01463            if (IsH264(stream))
 1464            {
 01465                return "-bsf:v h264_mp4toannexb";
 1466            }
 1467
 01468            if (IsAAC(stream))
 1469            {
 1470                // Convert adts header(mpegts) to asc header(mp4).
 01471                return "-bsf:a aac_adtstoasc";
 1472            }
 1473
 01474            if (IsH265(stream))
 1475            {
 01476                var filter = "-bsf:v hevc_mp4toannexb";
 1477
 1478                // The following checks are not complete because the copy would be rejected
 1479                // if the encoder cannot remove required metadata.
 1480                // And if bsf is used, we must already be using copy codec.
 01481                switch (ShouldRemoveDynamicHdrMetadata(state))
 1482                {
 1483                    default:
 1484                    case DynamicHdrMetadataRemovalPlan.None:
 1485                        break;
 1486                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01487                        filter += _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.HevcMetadata
 01488                            ? ",hevc_metadata=remove_dovi=1"
 01489                            : ",dovi_rpu=strip=1";
 01490                        break;
 1491                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01492                        filter += ",hevc_metadata=remove_hdr10plus=1";
 1493                        break;
 1494                }
 1495
 01496                return filter;
 1497            }
 1498
 01499            if (IsAv1(stream))
 1500            {
 01501                switch (ShouldRemoveDynamicHdrMetadata(state))
 1502                {
 1503                    default:
 1504                    case DynamicHdrMetadataRemovalPlan.None:
 01505                        return null;
 1506                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01507                        return _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.Av1MetadataRemo
 01508                            ? "-bsf:v av1_metadata=remove_dovi=1"
 01509                            : "-bsf:v dovi_rpu=strip=1";
 1510                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01511                        return "-bsf:v av1_metadata=remove_hdr10plus=1";
 1512                }
 1513            }
 1514
 01515            return null;
 1516        }
 1517
 1518        public string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceConta
 1519        {
 01520            var bitStreamArgs = string.Empty;
 01521            var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
 1522
 1523            // Apply aac_adtstoasc bitstream filter when media source is in mpegts.
 01524            if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)
 01525                && (string.Equals(mediaSourceContainer, "ts", StringComparison.OrdinalIgnoreCase)
 01526                    || string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase)
 01527                    || string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase)))
 1528            {
 01529                bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Audio);
 01530                bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs;
 1531            }
 1532
 01533            return bitStreamArgs;
 1534        }
 1535
 1536        public static string GetSegmentFileExtension(string segmentContainer)
 1537        {
 01538            if (!string.IsNullOrWhiteSpace(segmentContainer))
 1539            {
 01540                return "." + segmentContainer;
 1541            }
 1542
 01543            return ".ts";
 1544        }
 1545
 1546        private string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
 1547        {
 01548            if (state.OutputVideoBitrate is null)
 1549            {
 01550                return string.Empty;
 1551            }
 1552
 01553            int bitrate = state.OutputVideoBitrate.Value;
 1554
 1555            // Bit rate under 1000k is not allowed in h264_qsv
 01556            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 1557            {
 01558                bitrate = Math.Max(bitrate, 1000);
 1559            }
 1560
 1561            // Currently use the same buffer size for all encoders
 01562            int bufsize = bitrate * 2;
 1563
 01564            if (string.Equals(videoCodec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1565            {
 01566                return FormattableString.Invariant($" -b:v {bitrate} -bufsize {bufsize}");
 1567            }
 1568
 01569            if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)
 01570                || string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
 1571            {
 01572                return FormattableString.Invariant($" -maxrate {bitrate} -bufsize {bufsize}");
 1573            }
 1574
 01575            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01576                || string.Equals(videoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 01577                || string.Equals(videoCodec, "av1_qsv", StringComparison.OrdinalIgnoreCase))
 1578            {
 1579                // TODO: probe QSV encoders' capabilities and enable more tuning options
 1580                // See also https://github.com/intel/media-delivery/blob/master/doc/quality.rst
 1581
 1582                // Enable MacroBlock level bitrate control for better subjective visual quality
 01583                var mbbrcOpt = string.Empty;
 01584                if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01585                    || string.Equals(videoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 1586                {
 01587                    mbbrcOpt = " -mbbrc 1";
 1588                }
 1589
 1590                // Set (maxrate == bitrate + 1) to trigger VBR for better bitrate allocation
 1591                // Set (rc_init_occupancy == 2 * bitrate) and (bufsize == 4 * bitrate) to deal with drastic scene change
 01592                return FormattableString.Invariant($"{mbbrcOpt} -b:v {bitrate} -maxrate {bitrate + 1} -rc_init_occupancy
 1593            }
 1594
 01595            if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01596                || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01597                || string.Equals(videoCodec, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1598            {
 1599                // Override the too high default qmin 18 in transcoding preset
 01600                return FormattableString.Invariant($" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsiz
 1601            }
 1602
 01603            if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01604                || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01605                || string.Equals(videoCodec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1606            {
 1607                // VBR in i965 driver may result in pixelated output.
 01608                if (_mediaEncoder.IsVaapiDeviceInteli965)
 1609                {
 01610                    return FormattableString.Invariant($" -rc_mode CBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsi
 1611                }
 1612
 01613                return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"
 1614            }
 1615
 01616            if (string.Equals(videoCodec, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 01617                || string.Equals(videoCodec, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase))
 1618            {
 1619                // The `maxrate` and `bufsize` options can potentially lead to performance regression
 1620                // and even encoder hangs, especially when the value is very high.
 01621                return FormattableString.Invariant($" -b:v {bitrate} -qmin -1 -qmax -1");
 1622            }
 1623
 01624            return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
 1625        }
 1626
 1627        private string GetEncoderParam(EncoderPreset? preset, EncoderPreset defaultPreset, EncodingOptions encodingOptio
 1628        {
 01629            var param = string.Empty;
 01630            var encoderPreset = preset ?? defaultPreset;
 01631            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
 1632            {
 01633                var presetString = encoderPreset switch
 01634                {
 01635                    EncoderPreset.auto => EncoderPreset.veryfast.ToString().ToLowerInvariant(),
 01636                    _ => encoderPreset.ToString().ToLowerInvariant()
 01637                };
 1638
 01639                param += " -preset " + presetString;
 1640
 01641                int encodeCrf = encodingOptions.H264Crf;
 01642                if (isLibX265)
 1643                {
 01644                    encodeCrf = encodingOptions.H265Crf;
 1645                }
 1646
 01647                if (encodeCrf >= 0 && encodeCrf <= 51)
 1648                {
 01649                    param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture);
 1650                }
 1651                else
 1652                {
 01653                    string defaultCrf = "23";
 01654                    if (isLibX265)
 1655                    {
 01656                        defaultCrf = "28";
 1657                    }
 1658
 01659                    param += " -crf " + defaultCrf;
 1660                }
 1661            }
 01662            else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1663            {
 1664                // Default to use the recommended preset 10.
 1665                // Omit presets < 5, which are too slow for on the fly encoding.
 1666                // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md
 01667                param += encoderPreset switch
 01668                {
 01669                    EncoderPreset.veryslow => " -preset 5",
 01670                    EncoderPreset.slower => " -preset 6",
 01671                    EncoderPreset.slow => " -preset 7",
 01672                    EncoderPreset.medium => " -preset 8",
 01673                    EncoderPreset.fast => " -preset 9",
 01674                    EncoderPreset.faster => " -preset 10",
 01675                    EncoderPreset.veryfast => " -preset 11",
 01676                    EncoderPreset.superfast => " -preset 12",
 01677                    EncoderPreset.ultrafast => " -preset 13",
 01678                    _ => " -preset 10"
 01679                };
 1680            }
 01681            else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01682                     || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01683                     || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1684            {
 1685                // -compression_level is not reliable on AMD.
 01686                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 1687                {
 01688                    param += encoderPreset switch
 01689                    {
 01690                        EncoderPreset.veryslow => " -compression_level 1",
 01691                        EncoderPreset.slower => " -compression_level 2",
 01692                        EncoderPreset.slow => " -compression_level 3",
 01693                        EncoderPreset.medium => " -compression_level 4",
 01694                        EncoderPreset.fast => " -compression_level 5",
 01695                        EncoderPreset.faster => " -compression_level 6",
 01696                        EncoderPreset.veryfast => " -compression_level 7",
 01697                        EncoderPreset.superfast => " -compression_level 7",
 01698                        EncoderPreset.ultrafast => " -compression_level 7",
 01699                        _ => string.Empty
 01700                    };
 1701                }
 1702            }
 01703            else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv)
 01704                     || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv)
 01705                     || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv)
 1706            {
 01707                EncoderPreset[] valid_presets = [EncoderPreset.veryslow, EncoderPreset.slower, EncoderPreset.slow, Encod
 1708
 01709                param += " -preset " + (valid_presets.Contains(encoderPreset) ? encoderPreset : EncoderPreset.veryfast).
 1710            }
 01711            else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
 01712                        || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_n
 01713                        || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase) // av1 (av1_nven
 01714            )
 1715            {
 01716                param += encoderPreset switch
 01717                {
 01718                        EncoderPreset.veryslow => " -preset p7",
 01719                        EncoderPreset.slower => " -preset p6",
 01720                        EncoderPreset.slow => " -preset p5",
 01721                        EncoderPreset.medium => " -preset p4",
 01722                        EncoderPreset.fast => " -preset p3",
 01723                        EncoderPreset.faster => " -preset p2",
 01724                        _ => " -preset p1"
 01725                };
 1726            }
 01727            else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf)
 01728                        || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf
 01729                        || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase) // av1 (av1_amf)
 01730            )
 1731            {
 01732                param += encoderPreset switch
 01733                {
 01734                        EncoderPreset.veryslow => " -quality quality",
 01735                        EncoderPreset.slower => " -quality quality",
 01736                        EncoderPreset.slow => " -quality quality",
 01737                        EncoderPreset.medium => " -quality balanced",
 01738                        _ => " -quality speed"
 01739                };
 1740
 01741                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01742                    || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1743                {
 01744                    param += " -header_insertion_mode gop";
 1745                }
 1746
 01747                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
 1748                {
 01749                    param += " -gops_per_idr 1";
 1750                }
 1751            }
 01752            else if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase) // h264 (h264_
 01753                        || string.Equals(videoEncoder, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase) // hevc 
 01754            )
 1755            {
 01756                param += encoderPreset switch
 01757                {
 01758                        EncoderPreset.veryslow => " -prio_speed 0",
 01759                        EncoderPreset.slower => " -prio_speed 0",
 01760                        EncoderPreset.slow => " -prio_speed 0",
 01761                        EncoderPreset.medium => " -prio_speed 0",
 01762                        _ => " -prio_speed 1"
 01763                };
 1764            }
 1765
 01766            return param;
 1767        }
 1768
 1769        public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
 1770        {
 01771            if (double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel))
 1772            {
 01773                if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase))
 1774                {
 1775                    // Transcode to level 5.3 (15) and lower for maximum compatibility.
 1776                    // https://en.wikipedia.org/wiki/AV1#Levels
 01777                    if (requestLevel < 0 || requestLevel >= 15)
 1778                    {
 01779                        return "15";
 1780                    }
 1781                }
 01782                else if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 01783                         || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
 1784                {
 1785                    // Transcode to level 5.0 and lower for maximum compatibility.
 1786                    // Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it.
 1787                    // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
 1788                    // MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880.
 01789                    if (requestLevel < 0 || requestLevel >= 150)
 1790                    {
 01791                        return "150";
 1792                    }
 1793                }
 01794                else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
 1795                {
 1796                    // Transcode to level 5.1 and lower for maximum compatibility.
 1797                    // h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
 1798                    // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
 01799                    if (requestLevel < 0 || requestLevel >= 51)
 1800                    {
 01801                        return "51";
 1802                    }
 1803                }
 1804            }
 1805
 01806            return level;
 1807        }
 1808
 1809        /// <summary>
 1810        /// Gets the text subtitle param.
 1811        /// </summary>
 1812        /// <param name="state">The state.</param>
 1813        /// <param name="enableAlpha">Enable alpha processing.</param>
 1814        /// <param name="enableSub2video">Enable sub2video mode.</param>
 1815        /// <returns>System.String.</returns>
 1816        public string GetTextSubtitlesFilter(EncodingJobInfo state, bool enableAlpha, bool enableSub2video)
 1817        {
 01818            var seconds = Math.Round(TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds);
 1819
 1820            // hls always copies timestamps
 01821            var setPtsParam = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive
 01822                ? string.Empty
 01823                : string.Format(CultureInfo.InvariantCulture, ",setpts=PTS -{0}/TB", seconds);
 1824
 01825            var alphaParam = enableAlpha ? ":alpha=1" : string.Empty;
 01826            var sub2videoParam = enableSub2video ? ":sub2video=1" : string.Empty;
 1827
 01828            var fontPath = _pathManager.GetAttachmentFolderPath(state.MediaSource.Id);
 01829            var fontParam = string.Format(
 01830                CultureInfo.InvariantCulture,
 01831                ":fontsdir='{0}'",
 01832                _mediaEncoder.EscapeSubtitleFilterPath(fontPath));
 1833
 01834            if (state.SubtitleStream.IsExternal)
 1835            {
 01836                var charsetParam = string.Empty;
 1837
 01838                if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
 1839                {
 01840                    var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
 01841                            state.SubtitleStream,
 01842                            state.SubtitleStream.Language,
 01843                            state.MediaSource,
 01844                            CancellationToken.None).GetAwaiter().GetResult();
 1845
 01846                    if (!string.IsNullOrEmpty(charenc))
 1847                    {
 01848                        charsetParam = ":charenc=" + charenc;
 1849                    }
 1850                }
 1851
 01852                return string.Format(
 01853                    CultureInfo.InvariantCulture,
 01854                    "subtitles=f='{0}'{1}{2}{3}{4}{5}",
 01855                    _mediaEncoder.EscapeSubtitleFilterPath(state.SubtitleStream.Path),
 01856                    charsetParam,
 01857                    alphaParam,
 01858                    sub2videoParam,
 01859                    fontParam,
 01860                    setPtsParam);
 1861            }
 1862
 01863            var subtitlePath = _subtitleEncoder.GetSubtitleFilePath(
 01864                    state.SubtitleStream,
 01865                    state.MediaSource,
 01866                    CancellationToken.None).GetAwaiter().GetResult();
 1867
 01868            return string.Format(
 01869                CultureInfo.InvariantCulture,
 01870                "subtitles=f='{0}'{1}{2}{3}{4}",
 01871                _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
 01872                alphaParam,
 01873                sub2videoParam,
 01874                fontParam,
 01875                setPtsParam);
 1876        }
 1877
 1878        public double? GetFramerateParam(EncodingJobInfo state)
 1879        {
 01880            var request = state.BaseRequest;
 1881
 01882            if (request.Framerate.HasValue)
 1883            {
 01884                return request.Framerate.Value;
 1885            }
 1886
 01887            var maxrate = request.MaxFramerate;
 1888
 01889            if (maxrate.HasValue && state.VideoStream is not null)
 1890            {
 01891                var contentRate = state.VideoStream.ReferenceFrameRate;
 1892
 01893                if (contentRate.HasValue && contentRate.Value > maxrate.Value)
 1894                {
 01895                    return maxrate;
 1896                }
 1897            }
 1898
 01899            return null;
 1900        }
 1901
 1902        public string GetHlsVideoKeyFrameArguments(
 1903            EncodingJobInfo state,
 1904            string codec,
 1905            int segmentLength,
 1906            bool isEventPlaylist,
 1907            int? startNumber)
 1908        {
 01909            var args = string.Empty;
 01910            var gopArg = string.Empty;
 1911
 01912            var keyFrameArg = string.Format(
 01913                CultureInfo.InvariantCulture,
 01914                " -force_key_frames:0 \"expr:gte(t,n_forced*{0})\"",
 01915                segmentLength);
 1916
 01917            var framerate = state.VideoStream?.RealFrameRate;
 01918            if (framerate.HasValue)
 1919            {
 1920                // This is to make sure keyframe interval is limited to our segment,
 1921                // as forcing keyframes is not enough.
 1922                // Example: we encoded half of desired length, then codec detected
 1923                // scene cut and inserted a keyframe; next forced keyframe would
 1924                // be created outside of segment, which breaks seeking.
 01925                gopArg = string.Format(
 01926                    CultureInfo.InvariantCulture,
 01927                    " -g:v:0 {0} -keyint_min:v:0 {0}",
 01928                    Math.Ceiling(segmentLength * framerate.Value));
 1929            }
 1930
 1931            // Unable to force key frames using these encoders, set key frames by GOP.
 01932            if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01933                || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 01934                || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01935                || string.Equals(codec, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 01936                || string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 01937                || string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 01938                || string.Equals(codec, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase)
 01939                || string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 01940                || string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 01941                || string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase)
 01942                || string.Equals(codec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1943            {
 01944                args += gopArg;
 1945            }
 01946            else if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase)
 01947                     || string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)
 01948                     || string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01949                     || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01950                     || string.Equals(codec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1951            {
 01952                args += keyFrameArg;
 1953
 1954                // prevent the libx264 from post processing to break the set keyframe.
 01955                if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase))
 1956                {
 01957                    args += " -sc_threshold:v:0 0";
 1958                }
 1959            }
 1960            else
 1961            {
 01962                args += keyFrameArg + gopArg;
 1963            }
 1964
 1965            // global_header produced by AMD HEVC VA-API encoder causes non-playable fMP4 on iOS
 01966            if (string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01967                && _mediaEncoder.IsVaapiDeviceAmd)
 1968            {
 01969                args += " -flags:v -global_header";
 1970            }
 1971
 01972            return args;
 1973        }
 1974
 1975        /// <summary>
 1976        /// Gets the video bitrate to specify on the command line.
 1977        /// </summary>
 1978        /// <param name="state">Encoding state.</param>
 1979        /// <param name="videoEncoder">Video encoder to use.</param>
 1980        /// <param name="encodingOptions">Encoding options.</param>
 1981        /// <param name="defaultPreset">Default present to use for encoding.</param>
 1982        /// <returns>Video bitrate.</returns>
 1983        public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, 
 1984        {
 01985            var param = string.Empty;
 1986
 1987            // Tutorials: Enable Intel GuC / HuC firmware loading for Low Power Encoding.
 1988            // https://01.org/group/43/downloads/firmware
 1989            // https://wiki.archlinux.org/title/intel_graphics#Enable_GuC_/_HuC_firmware_loading
 1990            // Intel Low Power Encoding can save unnecessary CPU-GPU synchronization,
 1991            // which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
 01992            var intelLowPowerHwEncoding = false;
 1993
 1994            // Workaround for linux 5.18 to 6.1.3 i915 hang at cost of performance.
 1995            // https://github.com/intel/media-driver/issues/1456
 01996            var enableWaFori915Hang = false;
 1997
 01998            var hardwareAccelerationType = encodingOptions.HardwareAccelerationType;
 1999
 02000            if (hardwareAccelerationType == HardwareAccelerationType.vaapi)
 2001            {
 02002                var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
 2003
 02004                if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
 2005                {
 02006                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder && isIntelVaapiDriver;
 2007                }
 02008                else if (string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
 2009                {
 02010                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder && isIntelVaapiDriver;
 2011                }
 2012            }
 02013            else if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 2014            {
 02015                if (OperatingSystem.IsLinux())
 2016                {
 02017                    var ver = Environment.OSVersion.Version;
 02018                    var isFixedKernel60 = ver.Major == 6 && ver.Minor == 0 && ver >= _minFixedKernel60i915Hang;
 02019                    var isUnaffectedKernel = ver < _minKerneli915Hang || ver > _maxKerneli915Hang;
 2020
 02021                    if (!(isUnaffectedKernel || isFixedKernel60))
 2022                    {
 02023                        var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
 02024                        var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
 02025                                             || vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 02026                        var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
 02027                            && IsVaapiSupported(state)
 02028                            && IsOpenclFullSupported()
 02029                            && !IsIntelVppTonemapAvailable(state, encodingOptions)
 02030                            && IsHwTonemapAvailable(state, encodingOptions);
 2031
 02032                        enableWaFori915Hang = isIntelDecoder && doOclTonemap;
 2033                    }
 2034                }
 2035
 02036                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 2037                {
 02038                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder;
 2039                }
 02040                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2041                {
 02042                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder;
 2043                }
 2044                else
 2045                {
 02046                    enableWaFori915Hang = false;
 2047                }
 2048            }
 2049
 02050            if (intelLowPowerHwEncoding)
 2051            {
 02052                param += " -low_power 1";
 2053            }
 2054
 02055            if (enableWaFori915Hang)
 2056            {
 02057                param += " -async_depth 1";
 2058            }
 2059
 02060            var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
 02061            var encodingPreset = encodingOptions.EncoderPreset;
 2062
 02063            param += GetEncoderParam(encodingPreset, defaultPreset, encodingOptions, videoEncoder, isLibX265);
 02064            param += GetVideoBitrateParam(state, videoEncoder);
 2065
 02066            var framerate = GetFramerateParam(state);
 02067            if (framerate.HasValue)
 2068            {
 02069                param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(CultureInfo.Inv
 2070            }
 2071
 02072            var targetVideoCodec = state.ActualOutputVideoCodec;
 02073            if (string.Equals(targetVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
 02074                || string.Equals(targetVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
 2075            {
 02076                targetVideoCodec = "hevc";
 2077            }
 2078
 02079            var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
 02080            profile = WhiteSpaceRegex().Replace(profile, string.Empty).ToLowerInvariant();
 2081
 02082            var videoProfiles = Array.Empty<string>();
 02083            if (string.Equals("h264", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2084            {
 02085                videoProfiles = _videoProfilesH264;
 2086            }
 02087            else if (string.Equals("hevc", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2088            {
 02089                videoProfiles = _videoProfilesH265;
 2090            }
 02091            else if (string.Equals("av1", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2092            {
 02093                videoProfiles = _videoProfilesAv1;
 2094            }
 2095
 02096            if (!videoProfiles.Contains(profile, StringComparison.OrdinalIgnoreCase))
 2097            {
 02098                profile = string.Empty;
 2099            }
 2100
 2101            // We only transcode to HEVC 8-bit for now, force Main Profile.
 02102            if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)
 02103                || profile.Contains("mainstill", StringComparison.OrdinalIgnoreCase))
 2104            {
 02105                profile = "main";
 2106            }
 2107
 2108            // Extended Profile is not supported by any known h264 encoders, force Main Profile.
 02109            if (profile.Contains("extended", StringComparison.OrdinalIgnoreCase))
 2110            {
 02111                profile = "main";
 2112            }
 2113
 2114            // Only libx264 support encoding H264 High 10 Profile, otherwise force High Profile.
 02115            if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02116                && profile.Contains("high10", StringComparison.OrdinalIgnoreCase))
 2117            {
 02118                profile = "high";
 2119            }
 2120
 2121            // We only need Main profile of AV1 encoders.
 02122            if (videoEncoder.Contains("av1", StringComparison.OrdinalIgnoreCase)
 02123                && (profile.Contains("high", StringComparison.OrdinalIgnoreCase)
 02124                    || profile.Contains("professional", StringComparison.OrdinalIgnoreCase)))
 2125            {
 02126                profile = "main";
 2127            }
 2128
 2129            // h264_vaapi does not support Baseline profile, force Constrained Baseline in this case,
 2130            // which is compatible (and ugly).
 02131            if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02132                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2133            {
 02134                profile = "constrained_baseline";
 2135            }
 2136
 2137            // libx264, h264_{qsv,nvenc,rkmpp} does not support Constrained Baseline profile, force Baseline in this cas
 02138            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02139                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02140                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02141                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02142                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2143            {
 02144                profile = "baseline";
 2145            }
 2146
 2147            // libx264, h264_{qsv,nvenc,vaapi,rkmpp} does not support Constrained High profile, force High in this case.
 02148            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02149                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02150                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02151                 || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02152                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02153                && profile.Contains("high", StringComparison.OrdinalIgnoreCase))
 2154            {
 02155                profile = "high";
 2156            }
 2157
 02158            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02159                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2160            {
 02161                profile = "constrained_baseline";
 2162            }
 2163
 02164            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02165                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2166            {
 02167                profile = "constrained_high";
 2168            }
 2169
 02170            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02171                && profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase))
 2172            {
 02173                profile = "constrained_baseline";
 2174            }
 2175
 02176            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02177                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2178            {
 02179                profile = "constrained_high";
 2180            }
 2181
 02182            if (!string.IsNullOrEmpty(profile))
 2183            {
 2184                // Currently there's no profile option in av1_nvenc encoder
 02185                if (!(string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 02186                      || string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)))
 2187                {
 02188                    param += " -profile:v:0 " + profile;
 2189                }
 2190            }
 2191
 02192            var level = state.GetRequestedLevel(targetVideoCodec);
 2193
 02194            if (!string.IsNullOrEmpty(level))
 2195            {
 02196                level = NormalizeTranscodingLevel(state, level);
 2197
 2198                // libx264, QSV, AMF can adjust the given level to match the output.
 02199                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02200                    || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2201                {
 02202                    param += " -level " + level;
 2203                }
 02204                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2205                {
 2206                    // hevc_qsv use -level 51 instead of -level 153.
 02207                    if (double.TryParse(level, CultureInfo.InvariantCulture, out double hevcLevel))
 2208                    {
 02209                        param += " -level " + (hevcLevel / 3);
 2210                    }
 2211                }
 02212                else if (string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 02213                         || string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 2214                {
 2215                    // libsvtav1 and av1_qsv use -level 60 instead of -level 16
 2216                    // https://aomedia.org/av1/specification/annex-a/
 02217                    if (int.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out int av1Level))
 2218                    {
 02219                        var x = 2 + (av1Level >> 2);
 02220                        var y = av1Level & 3;
 02221                        var res = (x * 10) + y;
 02222                        param += " -level " + res;
 2223                    }
 2224                }
 02225                else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02226                         || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 02227                         || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 2228                {
 02229                    param += " -level " + level;
 2230                }
 02231                else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02232                         || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 02233                         || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase))
 2234                {
 2235                    // level option may cause NVENC to fail.
 2236                    // NVENC cannot adjust the given level, just throw an error.
 2237                }
 02238                else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02239                         || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02240                         || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 2241                {
 2242                    // level option may cause corrupted frames on AMD VAAPI.
 02243                    if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 2244                    {
 02245                        param += " -level " + level;
 2246                    }
 2247                }
 02248                else if (string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 02249                         || string.Equals(videoEncoder, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase))
 2250                {
 02251                    param += " -level " + level;
 2252                }
 02253                else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2254                {
 02255                    param += " -level " + level;
 2256                }
 2257            }
 2258
 02259            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2260            {
 02261                param += " -x264opts:0 subme=0:me_range=16:rc_lookahead=10:me=hex:open_gop=0";
 2262            }
 2263
 02264            if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2265            {
 2266                // libx265 only accept level option in -x265-params.
 2267                // level option may cause libx265 to fail.
 2268                // libx265 cannot adjust the given level, just throw an error.
 02269                param += " -x265-params:0 no-scenecut=1:no-open-gop=1:no-info=1";
 2270
 02271                if (encodingOptions.EncoderPreset < EncoderPreset.ultrafast)
 2272                {
 2273                    // The following params are slower than the ultrafast preset, don't use when ultrafast is selected.
 02274                    param += ":subme=3:merange=25:rc-lookahead=10:me=star:ctu=32:max-tu-size=32:min-cu-size=16:rskip=2:r
 2275                }
 2276            }
 2277
 02278            if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)
 02279                && _mediaEncoder.EncoderVersion >= _minFFmpegSvtAv1Params)
 2280            {
 02281                param += " -svtav1-params:0 rc=1:tune=0:film-grain=0:enable-overlays=1:enable-tf=0";
 2282            }
 2283
 2284            /* Access unit too large: 8192 < 20880 error */
 02285            if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) ||
 02286                 string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) &&
 02287                 _mediaEncoder.EncoderVersion >= _minFFmpegVaapiH26xEncA53CcSei)
 2288            {
 02289                param += " -sei -a53_cc";
 2290            }
 2291
 02292            return param;
 2293        }
 2294
 2295        public bool CanStreamCopyVideo(EncodingJobInfo state, MediaStream videoStream)
 2296        {
 02297            var request = state.BaseRequest;
 2298
 02299            if (!request.AllowVideoStreamCopy)
 2300            {
 02301                return false;
 2302            }
 2303
 02304            if (videoStream.IsInterlaced
 02305                && state.DeInterlace(videoStream.Codec, false))
 2306            {
 02307                return false;
 2308            }
 2309
 02310            if (videoStream.IsAnamorphic ?? false)
 2311            {
 02312                if (request.RequireNonAnamorphic)
 2313                {
 02314                    return false;
 2315                }
 2316            }
 2317
 2318            // Can't stream copy if we're burning in subtitles
 02319            if (request.SubtitleStreamIndex.HasValue
 02320                && request.SubtitleStreamIndex.Value >= 0
 02321                && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
 2322            {
 02323                return false;
 2324            }
 2325
 02326            if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 02327                && videoStream.IsAVC.HasValue
 02328                && !videoStream.IsAVC.Value
 02329                && request.RequireAvc)
 2330            {
 02331                return false;
 2332            }
 2333
 2334            // Source and target codecs must match
 02335            if (string.IsNullOrEmpty(videoStream.Codec)
 02336                || (state.SupportedVideoCodecs.Length != 0
 02337                    && !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparison.OrdinalIgnoreCase)))
 2338            {
 02339                return false;
 2340            }
 2341
 02342            var requestedProfiles = state.GetRequestedProfiles(videoStream.Codec);
 2343
 2344            // If client is requesting a specific video profile, it must match the source
 02345            if (requestedProfiles.Length > 0)
 2346            {
 02347                if (string.IsNullOrEmpty(videoStream.Profile))
 2348                {
 2349                    // return false;
 2350                }
 2351
 02352                var requestedProfile = requestedProfiles[0];
 2353                // strip spaces because they may be stripped out on the query string as well
 02354                if (!string.IsNullOrEmpty(videoStream.Profile)
 02355                    && !requestedProfiles.Contains(videoStream.Profile.Replace(" ", string.Empty, StringComparison.Ordin
 2356                {
 02357                    var currentScore = GetVideoProfileScore(videoStream.Codec, videoStream.Profile);
 02358                    var requestedScore = GetVideoProfileScore(videoStream.Codec, requestedProfile);
 2359
 02360                    if (currentScore == -1 || currentScore > requestedScore)
 2361                    {
 02362                        return false;
 2363                    }
 2364                }
 2365            }
 2366
 02367            var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
 02368            if (requestedRangeTypes.Length > 0)
 2369            {
 02370                if (videoStream.VideoRangeType == VideoRangeType.Unknown)
 2371                {
 02372                    return false;
 2373                }
 2374
 2375                // DOVIWithHDR10 should be compatible with HDR10 supporting players. Same goes with HLG and of course SD
 02376                var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ord
 02377                var requestHasHLG = requestedRangeTypes.Contains(VideoRangeType.HLG.ToString(), StringComparison.Ordinal
 02378                var requestHasSDR = requestedRangeTypes.Contains(VideoRangeType.SDR.ToString(), StringComparison.Ordinal
 2379
 02380                if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreC
 02381                     && !((requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10)
 02382                            || (requestHasHLG && videoStream.VideoRangeType == VideoRangeType.DOVIWithHLG)
 02383                            || (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)
 02384                            || (requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.HDR10Plus)))
 2385                {
 2386                    // Check complicated cases where we need to remove dynamic metadata
 2387                    // Conservatively refuse to copy if the encoder can't remove dynamic metadata,
 2388                    // but a removal is required for compatability reasons.
 02389                    var dynamicHdrMetadataRemovalPlan = ShouldRemoveDynamicHdrMetadata(state);
 02390                    if (!CanEncoderRemoveDynamicHdrMetadata(dynamicHdrMetadataRemovalPlan, videoStream))
 2391                    {
 02392                        return false;
 2393                    }
 2394                }
 2395            }
 2396
 2397            // Video width must fall within requested value
 02398            if (request.MaxWidth.HasValue
 02399                && (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
 2400            {
 02401                return false;
 2402            }
 2403
 2404            // Video height must fall within requested value
 02405            if (request.MaxHeight.HasValue
 02406                && (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value))
 2407            {
 02408                return false;
 2409            }
 2410
 2411            // Video framerate must fall within requested value
 02412            var requestedFramerate = request.MaxFramerate ?? request.Framerate;
 02413            if (requestedFramerate.HasValue)
 2414            {
 02415                var videoFrameRate = videoStream.ReferenceFrameRate;
 2416
 2417                // Add a little tolerance to the framerate check because some videos might record a framerate
 2418                // that is slightly greater than the intended framerate, but the device can still play it correctly.
 2419                // 0.05 fps tolerance should be safe enough.
 02420                if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value + 0.05f)
 2421                {
 02422                    return false;
 2423                }
 2424            }
 2425
 2426            // Video bitrate must fall within requested value
 02427            if (request.VideoBitRate.HasValue
 02428                && (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value))
 2429            {
 2430                // For LiveTV that has no bitrate, let's try copy if other conditions are met
 02431                if (string.IsNullOrWhiteSpace(request.LiveStreamId) || videoStream.BitRate.HasValue)
 2432                {
 02433                    return false;
 2434                }
 2435            }
 2436
 02437            var maxBitDepth = state.GetRequestedVideoBitDepth(videoStream.Codec);
 02438            if (maxBitDepth.HasValue)
 2439            {
 02440                if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > maxBitDepth.Value)
 2441                {
 02442                    return false;
 2443                }
 2444            }
 2445
 02446            var maxRefFrames = state.GetRequestedMaxRefFrames(videoStream.Codec);
 02447            if (maxRefFrames.HasValue
 02448                && videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > maxRefFrames.Value)
 2449            {
 02450                return false;
 2451            }
 2452
 2453            // If a specific level was requested, the source must match or be less than
 02454            var level = state.GetRequestedLevel(videoStream.Codec);
 02455            if (double.TryParse(level, CultureInfo.InvariantCulture, out var requestLevel))
 2456            {
 02457                if (!videoStream.Level.HasValue)
 2458                {
 2459                    // return false;
 2460                }
 2461
 02462                if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
 2463                {
 02464                    return false;
 2465                }
 2466            }
 2467
 02468            if (string.Equals(state.InputContainer, "avi", StringComparison.OrdinalIgnoreCase)
 02469                && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)
 02470                && !(videoStream.IsAVC ?? false))
 2471            {
 2472                // see Coach S01E01 - Kelly and the Professor(0).avi
 02473                return false;
 2474            }
 2475
 02476            return true;
 2477        }
 2478
 2479        public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, IEnumerable<string> supportedAudi
 2480        {
 02481            var request = state.BaseRequest;
 2482
 02483            if (!request.AllowAudioStreamCopy)
 2484            {
 02485                return false;
 2486            }
 2487
 02488            var maxBitDepth = state.GetRequestedAudioBitDepth(audioStream.Codec);
 02489            if (maxBitDepth.HasValue
 02490                && audioStream.BitDepth.HasValue
 02491                && audioStream.BitDepth.Value > maxBitDepth.Value)
 2492            {
 02493                return false;
 2494            }
 2495
 2496            // Source and target codecs must match
 02497            if (string.IsNullOrEmpty(audioStream.Codec)
 02498                || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparison.OrdinalIgnoreCase))
 2499            {
 02500                return false;
 2501            }
 2502
 2503            // Channels must fall within requested value
 02504            var channels = state.GetRequestedAudioChannels(audioStream.Codec);
 02505            if (channels.HasValue)
 2506            {
 02507                if (!audioStream.Channels.HasValue || audioStream.Channels.Value <= 0)
 2508                {
 02509                    return false;
 2510                }
 2511
 02512                if (audioStream.Channels.Value > channels.Value)
 2513                {
 02514                    return false;
 2515                }
 2516            }
 2517
 2518            // Sample rate must fall within requested value
 02519            if (request.AudioSampleRate.HasValue)
 2520            {
 02521                if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value <= 0)
 2522                {
 02523                    return false;
 2524                }
 2525
 02526                if (audioStream.SampleRate.Value > request.AudioSampleRate.Value)
 2527                {
 02528                    return false;
 2529                }
 2530            }
 2531
 2532            // Audio bitrate must fall within requested value
 02533            if (request.AudioBitRate.HasValue
 02534                && audioStream.BitRate.HasValue
 02535                && audioStream.BitRate.Value > request.AudioBitRate.Value)
 2536            {
 02537                return false;
 2538            }
 2539
 02540            return request.EnableAutoStreamCopy;
 2541        }
 2542
 2543        public int GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideo
 2544        {
 02545            var bitrate = request.VideoBitRate;
 2546
 02547            if (videoStream is not null)
 2548            {
 02549                var isUpscaling = request.Height.HasValue
 02550                    && videoStream.Height.HasValue
 02551                    && request.Height.Value > videoStream.Height.Value
 02552                    && request.Width.HasValue
 02553                    && videoStream.Width.HasValue
 02554                    && request.Width.Value > videoStream.Width.Value;
 2555
 2556                // Don't allow bitrate increases unless upscaling
 02557                if (!isUpscaling && bitrate.HasValue && videoStream.BitRate.HasValue)
 2558                {
 02559                    bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value);
 2560                }
 2561
 02562                if (bitrate.HasValue)
 2563                {
 02564                    var inputVideoCodec = videoStream.Codec;
 02565                    bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
 2566
 2567                    // If a max bitrate was requested, don't let the scaled bitrate exceed it
 02568                    if (request.VideoBitRate.HasValue)
 2569                    {
 02570                        bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
 2571                    }
 2572                }
 2573            }
 2574
 2575            // Cap the max target bitrate to intMax/2 to satisfy the bufsize=bitrate*2.
 02576            return Math.Min(bitrate ?? 0, int.MaxValue / 2);
 2577        }
 2578
 2579        private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
 2580        {
 2581            // these values were chosen from testing to improve low bitrate streams
 02582            if (sourceBitrate <= 2000000)
 2583            {
 02584                sourceBitrate = Convert.ToInt32(sourceBitrate * 2.5);
 2585            }
 02586            else if (sourceBitrate <= 3000000)
 2587            {
 02588                sourceBitrate *= 2;
 2589            }
 2590
 02591            var bitrate = Math.Min(sourceBitrate, requestedBitrate);
 2592
 02593            return bitrate;
 2594        }
 2595
 2596        private static double GetVideoBitrateScaleFactor(string codec)
 2597        {
 2598            // hevc & vp9 - 40% more efficient than h.264
 02599            if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 02600                || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
 02601                || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
 2602            {
 02603                return .6;
 2604            }
 2605
 2606            // av1 - 50% more efficient than h.264
 02607            if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 2608            {
 02609                return .5;
 2610            }
 2611
 02612            return 1;
 2613        }
 2614
 2615        public static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
 2616        {
 02617            var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
 02618            var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
 2619
 2620            // Don't scale the real bitrate lower than the requested bitrate
 02621            var scaleFactor = Math.Max(outputScaleFactor / inputScaleFactor, 1);
 2622
 02623            if (bitrate <= 500000)
 2624            {
 02625                scaleFactor = Math.Max(scaleFactor, 4);
 2626            }
 02627            else if (bitrate <= 1000000)
 2628            {
 02629                scaleFactor = Math.Max(scaleFactor, 3);
 2630            }
 02631            else if (bitrate <= 2000000)
 2632            {
 02633                scaleFactor = Math.Max(scaleFactor, 2.5);
 2634            }
 02635            else if (bitrate <= 3000000)
 2636            {
 02637                scaleFactor = Math.Max(scaleFactor, 2);
 2638            }
 02639            else if (bitrate >= 30000000)
 2640            {
 2641                // Don't scale beyond 30Mbps, it is hardly visually noticeable for most codecs with our prefer speed enc
 2642                // and will cause extremely high bitrate to be used for av1->h264 transcoding that will overload clients
 02643                scaleFactor = 1;
 2644            }
 2645
 02646            return Convert.ToInt32(scaleFactor * bitrate);
 2647        }
 2648
 2649        public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChanne
 2650        {
 02651            return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels);
 2652        }
 2653
 2654        public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudio
 2655        {
 02656            if (audioStream is null)
 2657            {
 02658                return null;
 2659            }
 2660
 02661            var inputChannels = audioStream.Channels ?? 0;
 02662            var outputChannels = outputAudioChannels ?? 0;
 02663            var bitrate = audioBitRate ?? int.MaxValue;
 2664
 02665            if (string.IsNullOrEmpty(audioCodec)
 02666                || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
 02667                || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
 02668                || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
 02669                || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
 02670                || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
 02671                || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
 2672            {
 02673                return (inputChannels, outputChannels) switch
 02674                {
 02675                    (>= 6, >= 6 or 0) => Math.Min(640000, bitrate),
 02676                    (> 0, > 0) => Math.Min(outputChannels * 128000, bitrate),
 02677                    (> 0, _) => Math.Min(inputChannels * 128000, bitrate),
 02678                    (_, _) => Math.Min(384000, bitrate)
 02679                };
 2680            }
 2681
 02682            if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
 02683                || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
 2684            {
 02685                return (inputChannels, outputChannels) switch
 02686                {
 02687                    (>= 6, >= 6 or 0) => Math.Min(768000, bitrate),
 02688                    (> 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
 02689                    (> 0, _) => Math.Min(inputChannels * 136000, bitrate),
 02690                    (_, _) => Math.Min(672000, bitrate)
 02691                };
 2692            }
 2693
 2694            // Empty bitrate area is not allow on iOS
 2695            // Default audio bitrate to 128K per channel if we don't have codec specific defaults
 2696            // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
 02697            return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
 2698        }
 2699
 2700        public string GetAudioVbrModeParam(string encoder, int bitrate, int channels)
 2701        {
 02702            var bitratePerChannel = bitrate / Math.Max(channels, 1);
 02703            if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
 2704            {
 02705                return " -vbr:a " + bitratePerChannel switch
 02706                {
 02707                    < 32000 => "1",
 02708                    < 48000 => "2",
 02709                    < 64000 => "3",
 02710                    < 96000 => "4",
 02711                    _ => "5"
 02712                };
 2713            }
 2714
 02715            if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase))
 2716            {
 2717                // lame's VBR is only good for a certain bitrate range
 2718                // For very low and very high bitrate, use abr mode
 02719                if (bitratePerChannel is < 122500 and > 48000)
 2720                {
 02721                    return " -qscale:a " + bitratePerChannel switch
 02722                    {
 02723                        < 64000 => "6",
 02724                        < 88000 => "4",
 02725                        < 112000 => "2",
 02726                        _ => "0"
 02727                    };
 2728                }
 2729
 02730                return " -abr:a 1" + " -b:a " + bitrate;
 2731            }
 2732
 02733            if (string.Equals(encoder, "aac_at", StringComparison.OrdinalIgnoreCase))
 2734            {
 2735                // aac_at's CVBR mode
 02736                return " -aac_at_mode:a 2" + " -b:a " + bitrate;
 2737            }
 2738
 02739            if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase))
 2740            {
 02741                return " -qscale:a " + bitratePerChannel switch
 02742                {
 02743                    < 40000 => "0",
 02744                    < 56000 => "2",
 02745                    < 80000 => "4",
 02746                    < 112000 => "6",
 02747                    _ => "8"
 02748                };
 2749            }
 2750
 02751            return null;
 2752        }
 2753
 2754        public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
 2755        {
 02756            var channels = state.OutputAudioChannels;
 2757
 02758            var filters = new List<string>();
 2759
 02760            if (channels is 2 && state.AudioStream?.Channels is > 2)
 2761            {
 02762                var hasDownMixFilter = DownMixAlgorithmsHelper.AlgorithmFilterStrings.TryGetValue((encodingOptions.DownM
 02763                if (hasDownMixFilter)
 2764                {
 02765                    filters.Add(downMixFilterString);
 2766                }
 2767
 02768                if (!encodingOptions.DownMixAudioBoost.Equals(1))
 2769                {
 02770                    filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
 2771                }
 2772            }
 2773
 02774            var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
 02775            if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && ShouldEncodeSubtitle(st
 2776            {
 02777                var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds;
 2778
 02779                filters.Add(
 02780                    string.Format(
 02781                        CultureInfo.InvariantCulture,
 02782                        "asetpts=PTS-{0}/TB",
 02783                        Math.Round(seconds)));
 2784            }
 2785
 02786            if (filters.Count > 0)
 2787            {
 02788                return " -af \"" + string.Join(',', filters) + "\"";
 2789            }
 2790
 02791            return string.Empty;
 2792        }
 2793
 2794        /// <summary>
 2795        /// Gets the number of audio channels to specify on the command line.
 2796        /// </summary>
 2797        /// <param name="state">The state.</param>
 2798        /// <param name="audioStream">The audio stream.</param>
 2799        /// <param name="outputAudioCodec">The output audio codec.</param>
 2800        /// <returns>System.Nullable{System.Int32}.</returns>
 2801        public int? GetNumAudioChannelsParam(EncodingJobInfo state, MediaStream audioStream, string outputAudioCodec)
 2802        {
 02803            if (audioStream is null)
 2804            {
 02805                return null;
 2806            }
 2807
 02808            var request = state.BaseRequest;
 2809
 02810            var codec = outputAudioCodec ?? string.Empty;
 2811
 02812            int? resultChannels = state.GetRequestedAudioChannels(codec);
 2813
 02814            var inputChannels = audioStream.Channels;
 2815
 02816            if (inputChannels > 0)
 2817            {
 02818                resultChannels = inputChannels < resultChannels ? inputChannels : resultChannels ?? inputChannels;
 2819            }
 2820
 02821            var isTranscodingAudio = !IsCopyCodec(codec);
 2822
 02823            if (isTranscodingAudio)
 2824            {
 02825                var audioEncoder = GetAudioEncoder(state);
 02826                if (!_audioTranscodeChannelLookup.TryGetValue(audioEncoder, out var transcoderChannelLimit))
 2827                {
 2828                    // Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many c
 02829                    transcoderChannelLimit = 8;
 2830                }
 2831
 2832                // Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelL
 02833                resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? tr
 2834
 02835                if (request.TranscodingMaxAudioChannels < resultChannels)
 2836                {
 02837                    resultChannels = request.TranscodingMaxAudioChannels;
 2838                }
 2839
 2840                // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
 2841                // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_d
 02842                if (state.TranscodingType != TranscodingJobType.Progressive
 02843                    && ((resultChannels > 2 && resultChannels < 6) || resultChannels == 7))
 2844                {
 2845                    // We can let FFMpeg supply an extra LFE channel for 5ch and 7ch to make them 5.1 and 7.1
 02846                    if (resultChannels == 5)
 2847                    {
 02848                        resultChannels = 6;
 2849                    }
 02850                    else if (resultChannels == 7)
 2851                    {
 02852                        resultChannels = 8;
 2853                    }
 2854                    else
 2855                    {
 2856                        // For other weird layout, just downmix to stereo for compatibility
 02857                        resultChannels = 2;
 2858                    }
 2859                }
 2860            }
 2861
 02862            return resultChannels;
 2863        }
 2864
 2865        /// <summary>
 2866        /// Enforces the resolution limit.
 2867        /// </summary>
 2868        /// <param name="state">The state.</param>
 2869        public void EnforceResolutionLimit(EncodingJobInfo state)
 2870        {
 02871            var videoRequest = state.BaseRequest;
 2872
 2873            // Switch the incoming params to be ceilings rather than fixed values
 02874            videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
 02875            videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
 2876
 02877            videoRequest.Width = null;
 02878            videoRequest.Height = null;
 02879        }
 2880
 2881        /// <summary>
 2882        /// Gets the fast seek command line parameter.
 2883        /// </summary>
 2884        /// <param name="state">The state.</param>
 2885        /// <param name="options">The options.</param>
 2886        /// <param name="segmentContainer">Segment Container.</param>
 2887        /// <returns>System.String.</returns>
 2888        /// <value>The fast seek command line parameter.</value>
 2889        public string GetFastSeekCommandLineParameter(EncodingJobInfo state, EncodingOptions options, string segmentCont
 2890        {
 02891            var time = state.BaseRequest.StartTimeTicks ?? 0;
 02892            var maxTime = state.RunTimeTicks ?? 0;
 02893            var seekParam = string.Empty;
 2894
 02895            if (time > 0)
 2896            {
 2897                // For direct streaming/remuxing, we seek at the exact position of the keyframe
 2898                // However, ffmpeg will seek to previous keyframe when the exact time is the input
 2899                // Workaround this by adding 0.5s offset to the seeking time to get the exact keyframe on most videos.
 2900                // This will help subtitle syncing.
 02901                var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCod
 02902                var seekTick = isHlsRemuxing ? time + 5000000L : time;
 2903
 2904                // Seeking beyond EOF makes no sense in transcoding. Clamp the seekTick value to
 2905                // [0, RuntimeTicks - 5.0s], so that the muxer gets packets and avoid error codes.
 02906                if (maxTime > 0)
 2907                {
 02908                    seekTick = Math.Clamp(seekTick, 0, Math.Max(maxTime - 50000000L, 0));
 2909                }
 2910
 02911                seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(seekT
 2912
 02913                if (state.IsVideoRequest)
 2914                {
 02915                    var outputVideoCodec = GetVideoEncoder(state, options);
 02916                    var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
 2917
 2918                    // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking
 2919                    // Disable -noaccurate_seek on mpegts container due to the timestamps issue on some clients,
 2920                    // but it's still required for fMP4 container otherwise the audio can't be synced to the video.
 02921                    if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase)
 02922                        && !string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase)
 02923                        && state.TranscodingType != TranscodingJobType.Progressive
 02924                        && !state.EnableBreakOnNonKeyFrames(outputVideoCodec)
 02925                        && (state.BaseRequest.StartTimeTicks ?? 0) > 0)
 2926                    {
 02927                        seekParam += " -noaccurate_seek";
 2928                    }
 2929                }
 2930            }
 2931
 02932            return seekParam;
 2933        }
 2934
 2935        /// <summary>
 2936        /// Gets the map args.
 2937        /// </summary>
 2938        /// <param name="state">The state.</param>
 2939        /// <returns>System.String.</returns>
 2940        public string GetMapArgs(EncodingJobInfo state)
 2941        {
 2942            // If we don't have known media info
 2943            // If input is video, use -sn to drop subtitles
 2944            // Otherwise just return empty
 02945            if (state.VideoStream is null && state.AudioStream is null)
 2946            {
 02947                return state.IsInputVideo ? "-sn" : string.Empty;
 2948            }
 2949
 2950            // We have media info, but we don't know the stream index
 02951            if (state.VideoStream is not null && state.VideoStream.Index == -1)
 2952            {
 02953                return "-sn";
 2954            }
 2955
 2956            // We have media info, but we don't know the stream index
 02957            if (state.AudioStream is not null && state.AudioStream.Index == -1)
 2958            {
 02959                return state.IsInputVideo ? "-sn" : string.Empty;
 2960            }
 2961
 02962            var args = string.Empty;
 2963
 02964            if (state.VideoStream is not null)
 2965            {
 02966                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 2967
 02968                args += string.Format(
 02969                    CultureInfo.InvariantCulture,
 02970                    "-map 0:{0}",
 02971                    videoStreamIndex);
 2972            }
 2973            else
 2974            {
 2975                // No known video stream
 02976                args += "-vn";
 2977            }
 2978
 02979            if (state.AudioStream is not null)
 2980            {
 02981                int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
 02982                if (state.AudioStream.IsExternal)
 2983                {
 02984                    bool hasExternalGraphicsSubs = state.SubtitleStream is not null
 02985                        && ShouldEncodeSubtitle(state)
 02986                        && state.SubtitleStream.IsExternal
 02987                        && !state.SubtitleStream.IsTextSubtitleStream;
 02988                    int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
 2989
 02990                    args += string.Format(
 02991                        CultureInfo.InvariantCulture,
 02992                        " -map {0}:{1}",
 02993                        externalAudioMapIndex,
 02994                        audioStreamIndex);
 2995                }
 2996                else
 2997                {
 02998                    args += string.Format(
 02999                        CultureInfo.InvariantCulture,
 03000                        " -map 0:{0}",
 03001                        audioStreamIndex);
 3002                }
 3003            }
 3004            else
 3005            {
 03006                args += " -map -0:a";
 3007            }
 3008
 03009            var subtitleMethod = state.SubtitleDeliveryMethod;
 03010            if (state.SubtitleStream is null || subtitleMethod == SubtitleDeliveryMethod.Hls)
 3011            {
 03012                args += " -map -0:s";
 3013            }
 03014            else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
 3015            {
 03016                int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3017
 03018                args += string.Format(
 03019                    CultureInfo.InvariantCulture,
 03020                    " -map 0:{0}",
 03021                    subtitleStreamIndex);
 3022            }
 03023            else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
 3024            {
 03025                int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3026
 03027                args += string.Format(
 03028                    CultureInfo.InvariantCulture,
 03029                    " -map 1:{0} -sn",
 03030                    externalSubtitleStreamIndex);
 3031            }
 3032
 03033            return args;
 3034        }
 3035
 3036        /// <summary>
 3037        /// Gets the negative map args by filters.
 3038        /// </summary>
 3039        /// <param name="state">The state.</param>
 3040        /// <param name="videoProcessFilters">The videoProcessFilters.</param>
 3041        /// <returns>System.String.</returns>
 3042        public string GetNegativeMapArgsByFilters(EncodingJobInfo state, string videoProcessFilters)
 3043        {
 03044            string args = string.Empty;
 3045
 3046            // http://ffmpeg.org/ffmpeg-all.html#toc-Complex-filtergraphs-1
 03047            if (state.VideoStream is not null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordina
 3048            {
 03049                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 3050
 03051                args += string.Format(
 03052                    CultureInfo.InvariantCulture,
 03053                    "-map -0:{0} ",
 03054                    videoStreamIndex);
 3055            }
 3056
 03057            return args;
 3058        }
 3059
 3060        /// <summary>
 3061        /// Determines which stream will be used for playback.
 3062        /// </summary>
 3063        /// <param name="allStream">All stream.</param>
 3064        /// <param name="desiredIndex">Index of the desired.</param>
 3065        /// <param name="type">The type.</param>
 3066        /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
 3067        /// <returns>MediaStream.</returns>
 3068        public MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, b
 3069        {
 03070            var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
 3071
 03072            if (desiredIndex.HasValue)
 3073            {
 03074                var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
 3075
 03076                if (stream is not null)
 3077                {
 03078                    return stream;
 3079                }
 3080            }
 3081
 03082            if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
 3083            {
 03084                return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
 03085                       streams.FirstOrDefault();
 3086            }
 3087
 3088            // Just return the first one
 03089            return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
 3090        }
 3091
 3092        public static (int? Width, int? Height) GetFixedOutputSize(
 3093            int? videoWidth,
 3094            int? videoHeight,
 3095            int? requestedWidth,
 3096            int? requestedHeight,
 3097            int? requestedMaxWidth,
 3098            int? requestedMaxHeight)
 3099        {
 03100            if (!videoWidth.HasValue && !requestedWidth.HasValue)
 3101            {
 03102                return (null, null);
 3103            }
 3104
 03105            if (!videoHeight.HasValue && !requestedHeight.HasValue)
 3106            {
 03107                return (null, null);
 3108            }
 3109
 03110            int inputWidth = Convert.ToInt32(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture);
 03111            int inputHeight = Convert.ToInt32(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture);
 03112            int outputWidth = requestedWidth ?? inputWidth;
 03113            int outputHeight = requestedHeight ?? inputHeight;
 3114
 3115            // Don't transcode video to bigger than 4k when using HW.
 03116            int maximumWidth = Math.Min(requestedMaxWidth ?? outputWidth, 4096);
 03117            int maximumHeight = Math.Min(requestedMaxHeight ?? outputHeight, 4096);
 3118
 03119            if (outputWidth > maximumWidth || outputHeight > maximumHeight)
 3120            {
 03121                var scaleW = (double)maximumWidth / outputWidth;
 03122                var scaleH = (double)maximumHeight / outputHeight;
 03123                var scale = Math.Min(scaleW, scaleH);
 03124                outputWidth = Math.Min(maximumWidth, Convert.ToInt32(outputWidth * scale));
 03125                outputHeight = Math.Min(maximumHeight, Convert.ToInt32(outputHeight * scale));
 3126            }
 3127
 03128            outputWidth = 2 * (outputWidth / 2);
 03129            outputHeight = 2 * (outputHeight / 2);
 3130
 03131            return (outputWidth, outputHeight);
 3132        }
 3133
 3134        public static bool IsScaleRatioSupported(
 3135            int? videoWidth,
 3136            int? videoHeight,
 3137            int? requestedWidth,
 3138            int? requestedHeight,
 3139            int? requestedMaxWidth,
 3140            int? requestedMaxHeight,
 3141            double? maxScaleRatio)
 3142        {
 03143            var (outWidth, outHeight) = GetFixedOutputSize(
 03144                videoWidth,
 03145                videoHeight,
 03146                requestedWidth,
 03147                requestedHeight,
 03148                requestedMaxWidth,
 03149                requestedMaxHeight);
 3150
 03151            if (!videoWidth.HasValue
 03152                 || !videoHeight.HasValue
 03153                 || !outWidth.HasValue
 03154                 || !outHeight.HasValue
 03155                 || !maxScaleRatio.HasValue
 03156                 || (maxScaleRatio.Value < 1.0f))
 3157            {
 03158                return false;
 3159            }
 3160
 03161            var minScaleRatio = 1.0f / maxScaleRatio;
 03162            var scaleRatioW = (double)outWidth / (double)videoWidth;
 03163            var scaleRatioH = (double)outHeight / (double)videoHeight;
 3164
 03165            if (scaleRatioW < minScaleRatio
 03166                || scaleRatioW > maxScaleRatio
 03167                || scaleRatioH < minScaleRatio
 03168                || scaleRatioH > maxScaleRatio)
 3169            {
 03170                return false;
 3171            }
 3172
 03173            return true;
 3174        }
 3175
 3176        public static string GetHwScaleFilter(
 3177            string hwScalePrefix,
 3178            string hwScaleSuffix,
 3179            string videoFormat,
 3180            bool swapOutputWandH,
 3181            int? videoWidth,
 3182            int? videoHeight,
 3183            int? requestedWidth,
 3184            int? requestedHeight,
 3185            int? requestedMaxWidth,
 3186            int? requestedMaxHeight)
 3187        {
 03188            var (outWidth, outHeight) = GetFixedOutputSize(
 03189                videoWidth,
 03190                videoHeight,
 03191                requestedWidth,
 03192                requestedHeight,
 03193                requestedMaxWidth,
 03194                requestedMaxHeight);
 3195
 03196            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03197            var isSizeFixed = !videoWidth.HasValue
 03198                || outWidth.Value != videoWidth.Value
 03199                || !videoHeight.HasValue
 03200                || outHeight.Value != videoHeight.Value;
 3201
 03202            var swpOutW = swapOutputWandH ? outHeight.Value : outWidth.Value;
 03203            var swpOutH = swapOutputWandH ? outWidth.Value : outHeight.Value;
 3204
 03205            var arg1 = isSizeFixed ? $"=w={swpOutW}:h={swpOutH}" : string.Empty;
 03206            var arg2 = isFormatFixed ? $"format={videoFormat}" : string.Empty;
 03207            if (isFormatFixed)
 3208            {
 03209                arg2 = (isSizeFixed ? ':' : '=') + arg2;
 3210            }
 3211
 03212            if (!string.IsNullOrEmpty(hwScaleSuffix) && (isSizeFixed || isFormatFixed))
 3213            {
 03214                return string.Format(
 03215                    CultureInfo.InvariantCulture,
 03216                    "{0}_{1}{2}{3}",
 03217                    hwScalePrefix ?? "scale",
 03218                    hwScaleSuffix,
 03219                    arg1,
 03220                    arg2);
 3221            }
 3222
 03223            return string.Empty;
 3224        }
 3225
 3226        public static string GetGraphicalSubPreProcessFilters(
 3227            int? videoWidth,
 3228            int? videoHeight,
 3229            int? subtitleWidth,
 3230            int? subtitleHeight,
 3231            int? requestedWidth,
 3232            int? requestedHeight,
 3233            int? requestedMaxWidth,
 3234            int? requestedMaxHeight)
 3235        {
 03236            var (outWidth, outHeight) = GetFixedOutputSize(
 03237                videoWidth,
 03238                videoHeight,
 03239                requestedWidth,
 03240                requestedHeight,
 03241                requestedMaxWidth,
 03242                requestedMaxHeight);
 3243
 03244            if (!outWidth.HasValue
 03245                || !outHeight.HasValue
 03246                || outWidth.Value <= 0
 03247                || outHeight.Value <= 0)
 3248            {
 03249                return string.Empty;
 3250            }
 3251
 3252            // Automatically add padding based on subtitle input
 03253            var filters = @"scale,scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:blac
 3254
 03255            if (subtitleWidth.HasValue
 03256                && subtitleHeight.HasValue
 03257                && subtitleWidth.Value > 0
 03258                && subtitleHeight.Value > 0)
 3259            {
 03260                var videoDar = (double)outWidth.Value / outHeight.Value;
 03261                var subtitleDar = (double)subtitleWidth.Value / subtitleHeight.Value;
 3262
 3263                // No need to add padding when DAR is the same -> 1080p PGSSUB on 2160p video
 03264                if (Math.Abs(videoDar - subtitleDar) < 0.01f)
 3265                {
 03266                    filters = @"scale,scale={0}:{1}:fast_bilinear";
 3267                }
 3268            }
 3269
 03270            return string.Format(
 03271                CultureInfo.InvariantCulture,
 03272                filters,
 03273                outWidth.Value,
 03274                outHeight.Value);
 3275        }
 3276
 3277        public static string GetAlphaSrcFilter(
 3278            EncodingJobInfo state,
 3279            int? videoWidth,
 3280            int? videoHeight,
 3281            int? requestedWidth,
 3282            int? requestedHeight,
 3283            int? requestedMaxWidth,
 3284            int? requestedMaxHeight,
 3285            float? framerate)
 3286        {
 03287            var reqTicks = state.BaseRequest.StartTimeTicks ?? 0;
 03288            var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture
 03289            var (outWidth, outHeight) = GetFixedOutputSize(
 03290                videoWidth,
 03291                videoHeight,
 03292                requestedWidth,
 03293                requestedHeight,
 03294                requestedMaxWidth,
 03295                requestedMaxHeight);
 3296
 03297            if (outWidth.HasValue && outHeight.HasValue)
 3298            {
 03299                return string.Format(
 03300                    CultureInfo.InvariantCulture,
 03301                    "alphasrc=s={0}x{1}:r={2}:start='{3}'",
 03302                    outWidth.Value,
 03303                    outHeight.Value,
 03304                    framerate ?? 25,
 03305                    reqTicks > 0 ? startTime : 0);
 3306            }
 3307
 03308            return string.Empty;
 3309        }
 3310
 3311        public static string GetSwScaleFilter(
 3312            EncodingJobInfo state,
 3313            EncodingOptions options,
 3314            string videoEncoder,
 3315            int? videoWidth,
 3316            int? videoHeight,
 3317            Video3DFormat? threedFormat,
 3318            int? requestedWidth,
 3319            int? requestedHeight,
 3320            int? requestedMaxWidth,
 3321            int? requestedMaxHeight)
 3322        {
 03323            var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 03324            var isMjpeg = videoEncoder is not null && videoEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase)
 03325            var scaleVal = isV4l2 ? 64 : 2;
 03326            var targetAr = isMjpeg ? "(a*sar)" : "a"; // manually calculate AR when using mjpeg encoder
 3327
 3328            // If fixed dimensions were supplied
 03329            if (requestedWidth.HasValue && requestedHeight.HasValue)
 3330            {
 03331                if (isV4l2)
 3332                {
 03333                    var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 03334                    var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3335
 03336                    return string.Format(
 03337                            CultureInfo.InvariantCulture,
 03338                            "scale=trunc({0}/64)*64:trunc({1}/2)*2",
 03339                            widthParam,
 03340                            heightParam);
 3341                }
 3342
 03343                return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
 3344            }
 3345
 3346            // If Max dimensions were supplied, for width selects lowest even number between input width and width req s
 3347
 03348            if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
 3349            {
 03350                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 03351                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3352
 03353                return string.Format(
 03354                    CultureInfo.InvariantCulture,
 03355                    @"scale=trunc(min(max(iw\,ih*{3})\,min({0}\,{1}*{3}))/{2})*{2}:trunc(min(max(iw/{3}\,ih)\,min({0}/{3
 03356                    maxWidthParam,
 03357                    maxHeightParam,
 03358                    scaleVal,
 03359                    targetAr);
 3360            }
 3361
 3362            // If a fixed width was requested
 03363            if (requestedWidth.HasValue)
 3364            {
 03365                if (threedFormat.HasValue)
 3366                {
 3367                    // This method can handle 0 being passed in for the requested height
 03368                    return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0);
 3369                }
 3370
 03371                var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 3372
 03373                return string.Format(
 03374                    CultureInfo.InvariantCulture,
 03375                    "scale={0}:trunc(ow/{1}/2)*2",
 03376                    widthParam,
 03377                    targetAr);
 3378            }
 3379
 3380            // If a fixed height was requested
 03381            if (requestedHeight.HasValue)
 3382            {
 03383                var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3384
 03385                return string.Format(
 03386                    CultureInfo.InvariantCulture,
 03387                    "scale=trunc(oh*{2}/{1})*{1}:{0}",
 03388                    heightParam,
 03389                    scaleVal,
 03390                    targetAr);
 3391            }
 3392
 3393            // If a max width was requested
 03394            if (requestedMaxWidth.HasValue)
 3395            {
 03396                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 3397
 03398                return string.Format(
 03399                    CultureInfo.InvariantCulture,
 03400                    @"scale=trunc(min(max(iw\,ih*{2})\,{0})/{1})*{1}:trunc(ow/{2}/2)*2",
 03401                    maxWidthParam,
 03402                    scaleVal,
 03403                    targetAr);
 3404            }
 3405
 3406            // If a max height was requested
 03407            if (requestedMaxHeight.HasValue)
 3408            {
 03409                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3410
 03411                return string.Format(
 03412                    CultureInfo.InvariantCulture,
 03413                    @"scale=trunc(oh*{2}/{1})*{1}:min(max(iw/{2}\,ih)\,{0})",
 03414                    maxHeightParam,
 03415                    scaleVal,
 03416                    targetAr);
 3417            }
 3418
 03419            return string.Empty;
 3420        }
 3421
 3422        private static string GetFixedSwScaleFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight
 3423        {
 03424            var widthParam = requestedWidth.ToString(CultureInfo.InvariantCulture);
 03425            var heightParam = requestedHeight.ToString(CultureInfo.InvariantCulture);
 3426
 03427            string filter = null;
 3428
 03429            if (threedFormat.HasValue)
 3430            {
 03431                switch (threedFormat.Value)
 3432                {
 3433                    case Video3DFormat.HalfSideBySide:
 03434                        filter = @"crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(i
 3435                        // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars
 03436                        break;
 3437                    case Video3DFormat.FullSideBySide:
 03438                        filter = @"crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3439                        // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the s
 03440                        break;
 3441                    case Video3DFormat.HalfTopAndBottom:
 03442                        filter = @"crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(
 3443                        // htab crop height in half,scale to correct size, set the display aspect,crop out any black bar
 03444                        break;
 3445                    case Video3DFormat.FullTopAndBottom:
 03446                        filter = @"crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3447                        // ftab crop height in half, set the display aspect,crop out any black bars we may have made the
 3448                        break;
 3449                    default:
 3450                        break;
 3451                }
 3452            }
 3453
 3454            // default
 03455            if (filter is null)
 3456            {
 03457                if (requestedHeight > 0)
 3458                {
 03459                    filter = "scale=trunc({0}/2)*2:trunc({1}/2)*2";
 3460                }
 3461                else
 3462                {
 03463                    filter = "scale={0}:trunc({0}/a/2)*2";
 3464                }
 3465            }
 3466
 03467            return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam);
 3468        }
 3469
 3470        public static string GetSwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options)
 3471        {
 03472            var doubleRateDeint = options.DeinterlaceDoubleRate && state.VideoStream?.ReferenceFrameRate <= 30;
 03473            return string.Format(
 03474                CultureInfo.InvariantCulture,
 03475                "{0}={1}:-1:0",
 03476                options.DeinterlaceMethod.ToString().ToLowerInvariant(),
 03477                doubleRateDeint ? "1" : "0");
 3478        }
 3479
 3480        public string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix)
 3481        {
 03482            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03483            if (hwDeintSuffix.Contains("cuda", StringComparison.OrdinalIgnoreCase))
 3484            {
 03485                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3486
 03487                return string.Format(
 03488                    CultureInfo.InvariantCulture,
 03489                    "{0}_cuda={1}:-1:0",
 03490                    useBwdif ? "bwdif" : "yadif",
 03491                    doubleRateDeint ? "1" : "0");
 3492            }
 3493
 03494            if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
 3495            {
 03496                return string.Format(
 03497                    CultureInfo.InvariantCulture,
 03498                    "deinterlace_vaapi=rate={0}",
 03499                    doubleRateDeint ? "field" : "frame");
 3500            }
 3501
 03502            if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
 3503            {
 03504                return "deinterlace_qsv=mode=2";
 3505            }
 3506
 03507            if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
 3508            {
 03509                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3510
 03511                return string.Format(
 03512                    CultureInfo.InvariantCulture,
 03513                    "{0}_videotoolbox={1}:-1:0",
 03514                    useBwdif ? "bwdif" : "yadif",
 03515                    doubleRateDeint ? "1" : "0");
 3516            }
 3517
 03518            return string.Empty;
 3519        }
 3520
 3521        private string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat, bool forc
 3522        {
 03523            if (string.IsNullOrEmpty(hwTonemapSuffix))
 3524            {
 03525                return string.Empty;
 3526            }
 3527
 03528            var args = string.Empty;
 03529            var algorithm = options.TonemappingAlgorithm.ToString().ToLowerInvariant();
 03530            var mode = options.TonemappingMode.ToString().ToLowerInvariant();
 03531            var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 03532            var rangeString = range.ToString().ToLowerInvariant();
 3533
 03534            if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
 3535            {
 03536                var doVaVppProcamp = false;
 03537                var procampParams = string.Empty;
 03538                if (options.VppTonemappingBrightness != 0
 03539                    && options.VppTonemappingBrightness >= -100
 03540                    && options.VppTonemappingBrightness <= 100)
 3541                {
 03542                    procampParams += "procamp_vaapi=b={0}";
 03543                    doVaVppProcamp = true;
 3544                }
 3545
 03546                if (options.VppTonemappingContrast > 1
 03547                    && options.VppTonemappingContrast <= 10)
 3548                {
 03549                    procampParams += doVaVppProcamp ? ":c={1}" : "procamp_vaapi=c={1}";
 03550                    doVaVppProcamp = true;
 3551                }
 3552
 03553                args = procampParams + "{2}tonemap_vaapi=format={3}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
 3554
 03555                return string.Format(
 03556                        CultureInfo.InvariantCulture,
 03557                        args,
 03558                        options.VppTonemappingBrightness,
 03559                        options.VppTonemappingContrast,
 03560                        doVaVppProcamp ? "," : string.Empty,
 03561                        videoFormat ?? "nv12");
 3562            }
 3563            else
 3564            {
 03565                args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
 3566
 03567                var useLegacyTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode
 03568                                           && _legacyTonemapModes.Contains(options.TonemappingMode);
 3569
 03570                var useAdvancedTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegAdvancedTonemapMode
 03571                                              && _advancedTonemapModes.Contains(options.TonemappingMode);
 3572
 03573                if (useLegacyTonemapModes || useAdvancedTonemapModes)
 3574                {
 03575                    args += ":tonemap_mode={5}";
 3576                }
 3577
 03578                if (options.TonemappingParam != 0)
 3579                {
 03580                    args += ":param={6}";
 3581                }
 3582
 03583                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3584                {
 03585                    args += ":range={7}";
 3586                }
 3587            }
 3588
 03589            return string.Format(
 03590                    CultureInfo.InvariantCulture,
 03591                    args,
 03592                    hwTonemapSuffix,
 03593                    videoFormat ?? "nv12",
 03594                    algorithm,
 03595                    options.TonemappingPeak,
 03596                    options.TonemappingDesat,
 03597                    mode,
 03598                    options.TonemappingParam,
 03599                    rangeString);
 3600        }
 3601
 3602        private string GetLibplaceboFilter(
 3603            EncodingOptions options,
 3604            string videoFormat,
 3605            bool doTonemap,
 3606            int? videoWidth,
 3607            int? videoHeight,
 3608            int? requestedWidth,
 3609            int? requestedHeight,
 3610            int? requestedMaxWidth,
 3611            int? requestedMaxHeight,
 3612            bool forceFullRange)
 3613        {
 03614            var (outWidth, outHeight) = GetFixedOutputSize(
 03615                videoWidth,
 03616                videoHeight,
 03617                requestedWidth,
 03618                requestedHeight,
 03619                requestedMaxWidth,
 03620                requestedMaxHeight);
 3621
 03622            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03623            var isSizeFixed = !videoWidth.HasValue
 03624                || outWidth.Value != videoWidth.Value
 03625                || !videoHeight.HasValue
 03626                || outHeight.Value != videoHeight.Value;
 3627
 03628            var sizeArg = isSizeFixed ? (":w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty;
 03629            var formatArg = isFormatFixed ? (":format=" + videoFormat) : string.Empty;
 03630            var tonemapArg = string.Empty;
 3631
 03632            if (doTonemap)
 3633            {
 03634                var algorithm = options.TonemappingAlgorithm;
 03635                var algorithmString = "clip";
 03636                var mode = options.TonemappingMode;
 03637                var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 3638
 03639                if (algorithm == TonemappingAlgorithm.bt2390)
 3640                {
 03641                    algorithmString = "bt.2390";
 3642                }
 03643                else if (algorithm != TonemappingAlgorithm.none)
 3644                {
 03645                    algorithmString = algorithm.ToString().ToLowerInvariant();
 3646                }
 3647
 03648                tonemapArg = $":tonemapping={algorithmString}:peak_detect=0:color_primaries=bt709:color_trc=bt709:colors
 3649
 03650                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3651                {
 03652                    tonemapArg += ":range=" + range.ToString().ToLowerInvariant();
 3653                }
 3654            }
 3655
 03656            return string.Format(
 03657                CultureInfo.InvariantCulture,
 03658                "libplacebo=upscaler=none:downscaler=none{0}{1}{2}",
 03659                sizeArg,
 03660                formatArg,
 03661                tonemapArg);
 3662        }
 3663
 3664        public string GetVideoTransposeDirection(EncodingJobInfo state)
 3665        {
 03666            return (state.VideoStream?.Rotation ?? 0) switch
 03667            {
 03668                90 => "cclock",
 03669                180 => "reversal",
 03670                -90 => "clock",
 03671                -180 => "reversal",
 03672                _ => string.Empty
 03673            };
 3674        }
 3675
 3676        /// <summary>
 3677        /// Gets the parameter of software filter chain.
 3678        /// </summary>
 3679        /// <param name="state">Encoding state.</param>
 3680        /// <param name="options">Encoding options.</param>
 3681        /// <param name="vidEncoder">Video encoder to use.</param>
 3682        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3683        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetSwVidFilterChain(
 3684            EncodingJobInfo state,
 3685            EncodingOptions options,
 3686            string vidEncoder)
 3687        {
 03688            var inW = state.VideoStream?.Width;
 03689            var inH = state.VideoStream?.Height;
 03690            var reqW = state.BaseRequest.Width;
 03691            var reqH = state.BaseRequest.Height;
 03692            var reqMaxW = state.BaseRequest.MaxWidth;
 03693            var reqMaxH = state.BaseRequest.MaxHeight;
 03694            var threeDFormat = state.MediaSource.Video3DFormat;
 3695
 03696            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03697            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03698            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 03699            var isV4l2Encoder = vidEncoder.Contains("h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 3700
 03701            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03702            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03703            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03704            var doToneMap = IsSwTonemapAvailable(state, options);
 03705            var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI;
 3706
 03707            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03708            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03709            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 3710
 03711            var rotation = state.VideoStream?.Rotation ?? 0;
 03712            var swapWAndH = Math.Abs(rotation) == 90;
 03713            var swpInW = swapWAndH ? inH : inW;
 03714            var swpInH = swapWAndH ? inW : inH;
 3715
 3716            /* Make main filters for video stream */
 03717            var mainFilters = new List<string>();
 3718
 03719            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doToneMap));
 3720
 3721            // INPUT sw surface(memory/copy-back from vram)
 3722            // sw deint
 03723            if (doDeintH2645)
 3724            {
 03725                var deintFilter = GetSwDeinterlaceFilter(state, options);
 03726                mainFilters.Add(deintFilter);
 3727            }
 3728
 03729            var outFormat = isSwDecoder ? "yuv420p" : "nv12";
 03730            var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, r
 03731            if (isVaapiEncoder)
 3732            {
 03733                outFormat = "nv12";
 3734            }
 03735            else if (isV4l2Encoder)
 3736            {
 03737                outFormat = "yuv420p";
 3738            }
 3739
 3740            // sw scale
 03741            mainFilters.Add(swScaleFilter);
 3742
 3743            // sw tonemap
 03744            if (doToneMap)
 3745            {
 3746                // tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary
 03747                var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat;
 03748                var tonemapArgString = "tonemapx=tonemap={0}:desat={1}:peak={2}:t=bt709:m=bt709:p=bt709:format={3}";
 3749
 03750                if (options.TonemappingParam != 0)
 3751                {
 03752                    tonemapArgString += ":param={4}";
 3753                }
 3754
 03755                var range = options.TonemappingRange;
 03756                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3757                {
 03758                    tonemapArgString += ":range={5}";
 3759                }
 3760
 03761                var tonemapArgs = string.Format(
 03762                    CultureInfo.InvariantCulture,
 03763                    tonemapArgString,
 03764                    options.TonemappingAlgorithm,
 03765                    options.TonemappingDesat,
 03766                    options.TonemappingPeak,
 03767                    tonemapFormat,
 03768                    options.TonemappingParam,
 03769                    options.TonemappingRange);
 3770
 03771                mainFilters.Add(tonemapArgs);
 3772            }
 3773            else
 3774            {
 3775                // OUTPUT yuv420p/nv12 surface(memory)
 03776                mainFilters.Add("format=" + outFormat);
 3777            }
 3778
 3779            /* Make sub and overlay filters for subtitle stream */
 03780            var subFilters = new List<string>();
 03781            var overlayFilters = new List<string>();
 03782            if (hasTextSubs)
 3783            {
 3784                // subtitles=f='*.ass':alpha=0
 03785                var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03786                mainFilters.Add(textSubtitlesFilter);
 3787            }
 03788            else if (hasGraphicalSubs)
 3789            {
 03790                var subW = state.SubtitleStream?.Width;
 03791                var subH = state.SubtitleStream?.Height;
 03792                var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW
 03793                subFilters.Add(subPreProcFilters);
 03794                overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 3795            }
 3796
 03797            return (mainFilters, subFilters, overlayFilters);
 3798        }
 3799
 3800        /// <summary>
 3801        /// Gets the parameter of Nvidia NVENC filter chain.
 3802        /// </summary>
 3803        /// <param name="state">Encoding state.</param>
 3804        /// <param name="options">Encoding options.</param>
 3805        /// <param name="vidEncoder">Video encoder to use.</param>
 3806        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3807        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFilterChain(
 3808            EncodingJobInfo state,
 3809            EncodingOptions options,
 3810            string vidEncoder)
 3811        {
 03812            if (options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 3813            {
 03814                return (null, null, null);
 3815            }
 3816
 03817            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03818            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03819            var isSwEncoder = !vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 3820
 3821            // legacy cuvid pipeline(copy-back)
 03822            if ((isSwDecoder && isSwEncoder)
 03823                || !IsCudaFullSupported()
 03824                || !_mediaEncoder.SupportsFilter("alphasrc"))
 3825            {
 03826                return GetSwVidFilterChain(state, options, vidEncoder);
 3827            }
 3828
 3829            // preferred nvdec/cuvid + cuda filters + nvenc pipeline
 03830            return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 3831        }
 3832
 3833        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFiltersPrefe
 3834            EncodingJobInfo state,
 3835            EncodingOptions options,
 3836            string vidDecoder,
 3837            string vidEncoder)
 3838        {
 03839            var inW = state.VideoStream?.Width;
 03840            var inH = state.VideoStream?.Height;
 03841            var reqW = state.BaseRequest.Width;
 03842            var reqH = state.BaseRequest.Height;
 03843            var reqMaxW = state.BaseRequest.MaxWidth;
 03844            var reqMaxH = state.BaseRequest.MaxHeight;
 03845            var threeDFormat = state.MediaSource.Video3DFormat;
 3846
 03847            var isNvDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 03848            var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 03849            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03850            var isSwEncoder = !isNvencEncoder;
 03851            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 03852            var isCuInCuOut = isNvDecoder && isNvencEncoder;
 3853
 03854            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03855            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03856            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03857            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03858            var doCuTonemap = IsHwTonemapAvailable(state, options);
 3859
 03860            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03861            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03862            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 03863            var hasAssSubs = hasSubs
 03864                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 03865                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 03866            var subW = state.SubtitleStream?.Width;
 03867            var subH = state.SubtitleStream?.Height;
 3868
 03869            var rotation = state.VideoStream?.Rotation ?? 0;
 03870            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 03871            var doCuTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_cuda");
 03872            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isNvDecoder && doCuTranspose));
 03873            var swpInW = swapWAndH ? inH : inW;
 03874            var swpInH = swapWAndH ? inW : inH;
 3875
 3876            /* Make main filters for video stream */
 03877            var mainFilters = new List<string>();
 3878
 03879            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doCuTonemap));
 3880
 03881            if (isSwDecoder)
 3882            {
 3883                // INPUT sw surface(memory)
 3884                // sw deint
 03885                if (doDeintH2645)
 3886                {
 03887                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 03888                    mainFilters.Add(swDeintFilter);
 3889                }
 3890
 03891                var outFormat = doCuTonemap ? "yuv420p10le" : "yuv420p";
 03892                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 3893                // sw scale
 03894                mainFilters.Add(swScaleFilter);
 03895                mainFilters.Add($"format={outFormat}");
 3896
 3897                // sw => hw
 03898                if (doCuTonemap)
 3899                {
 03900                    mainFilters.Add("hwupload=derive_device=cuda");
 3901                }
 3902            }
 3903
 03904            if (isNvDecoder)
 3905            {
 3906                // INPUT cuda surface(vram)
 3907                // hw deint
 03908                if (doDeintH2645)
 3909                {
 03910                    var deintFilter = GetHwDeinterlaceFilter(state, options, "cuda");
 03911                    mainFilters.Add(deintFilter);
 3912                }
 3913
 3914                // hw transpose
 03915                if (doCuTranspose)
 3916                {
 03917                    mainFilters.Add($"transpose_cuda=dir={transposeDir}");
 3918                }
 3919
 03920                var isRext = IsVideoStreamHevcRext(state);
 03921                var outFormat = doCuTonemap ? (isRext ? "p010" : string.Empty) : "yuv420p";
 03922                var hwScaleFilter = GetHwScaleFilter("scale", "cuda", outFormat, false, swpInW, swpInH, reqW, reqH, reqM
 3923                // hw scale
 03924                mainFilters.Add(hwScaleFilter);
 3925            }
 3926
 3927            // hw tonemap
 03928            if (doCuTonemap)
 3929            {
 03930                var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p", isMjpegEncoder);
 03931                mainFilters.Add(tonemapFilter);
 3932            }
 3933
 03934            var memoryOutput = false;
 03935            var isUploadForCuTonemap = isSwDecoder && doCuTonemap;
 03936            if ((isNvDecoder && isSwEncoder) || (isUploadForCuTonemap && hasSubs))
 3937            {
 03938                memoryOutput = true;
 3939
 3940                // OUTPUT yuv420p surface(memory)
 03941                mainFilters.Add("hwdownload");
 03942                mainFilters.Add("format=yuv420p");
 3943            }
 3944
 3945            // OUTPUT yuv420p surface(memory)
 03946            if (isSwDecoder && isNvencEncoder && !isUploadForCuTonemap)
 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
 3961            // OUTPUT cuda(yuv420p) surface(vram)
 3962
 3963            /* Make sub and overlay filters for subtitle stream */
 03964            var subFilters = new List<string>();
 03965            var overlayFilters = new List<string>();
 03966            if (isCuInCuOut)
 3967            {
 03968                if (hasSubs)
 3969                {
 03970                    var alphaFormatOpt = string.Empty;
 03971                    if (hasGraphicalSubs)
 3972                    {
 03973                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 03974                        subFilters.Add(subPreProcFilters);
 03975                        subFilters.Add("format=yuva420p");
 3976                    }
 03977                    else if (hasTextSubs)
 3978                    {
 03979                        var framerate = state.VideoStream?.RealFrameRate;
 03980                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 3981
 3982                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 03983                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 03984                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 03985                        subFilters.Add(alphaSrcFilter);
 03986                        subFilters.Add("format=yuva420p");
 03987                        subFilters.Add(subTextSubtitlesFilter);
 3988
 03989                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayCudaAlphaFormat)
 03990                            ? ":alpha_format=premultiplied" : string.Empty;
 3991                    }
 3992
 03993                    subFilters.Add("hwupload=derive_device=cuda");
 03994                    overlayFilters.Add($"overlay_cuda=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 3995                }
 3996            }
 3997            else
 3998            {
 03999                if (hasGraphicalSubs)
 4000                {
 04001                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04002                    subFilters.Add(subPreProcFilters);
 04003                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4004                }
 4005            }
 4006
 04007            return (mainFilters, subFilters, overlayFilters);
 4008        }
 4009
 4010        /// <summary>
 4011        /// Gets the parameter of AMD AMF filter chain.
 4012        /// </summary>
 4013        /// <param name="state">Encoding state.</param>
 4014        /// <param name="options">Encoding options.</param>
 4015        /// <param name="vidEncoder">Video encoder to use.</param>
 4016        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4017        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVidFilterChain(
 4018            EncodingJobInfo state,
 4019            EncodingOptions options,
 4020            string vidEncoder)
 4021        {
 04022            if (options.HardwareAccelerationType != HardwareAccelerationType.amf)
 4023            {
 04024                return (null, null, null);
 4025            }
 4026
 04027            var isWindows = OperatingSystem.IsWindows();
 04028            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04029            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04030            var isSwEncoder = !vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04031            var isAmfDx11OclSupported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va") && IsOpenclFullSupported()
 4032
 4033            // legacy d3d11va pipeline(copy-back)
 04034            if ((isSwDecoder && isSwEncoder)
 04035                || !isAmfDx11OclSupported
 04036                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4037            {
 04038                return GetSwVidFilterChain(state, options, vidEncoder);
 4039            }
 4040
 4041            // preferred d3d11va + opencl filters + amf pipeline
 04042            return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4043        }
 4044
 4045        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdDx11VidFiltersPref
 4046            EncodingJobInfo state,
 4047            EncodingOptions options,
 4048            string vidDecoder,
 4049            string vidEncoder)
 4050        {
 04051            var inW = state.VideoStream?.Width;
 04052            var inH = state.VideoStream?.Height;
 04053            var reqW = state.BaseRequest.Width;
 04054            var reqH = state.BaseRequest.Height;
 04055            var reqMaxW = state.BaseRequest.MaxWidth;
 04056            var reqMaxH = state.BaseRequest.MaxHeight;
 04057            var threeDFormat = state.MediaSource.Video3DFormat;
 4058
 04059            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04060            var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04061            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04062            var isSwEncoder = !isAmfEncoder;
 04063            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04064            var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder;
 4065
 04066            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04067            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04068            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04069            var doOclTonemap = IsHwTonemapAvailable(state, options);
 4070
 04071            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04072            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04073            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04074            var hasAssSubs = hasSubs
 04075                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04076                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04077            var subW = state.SubtitleStream?.Width;
 04078            var subH = state.SubtitleStream?.Height;
 4079
 04080            var rotation = state.VideoStream?.Rotation ?? 0;
 04081            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04082            var doOclTranspose = !string.IsNullOrEmpty(transposeDir)
 04083                && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TransposeOpenclReversal);
 04084            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isD3d11vaDecoder && doOclTranspose));
 04085            var swpInW = swapWAndH ? inH : inW;
 04086            var swpInH = swapWAndH ? inW : inH;
 4087
 4088            /* Make main filters for video stream */
 04089            var mainFilters = new List<string>();
 4090
 04091            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 4092
 04093            if (isSwDecoder)
 4094            {
 4095                // INPUT sw surface(memory)
 4096                // sw deint
 04097                if (doDeintH2645)
 4098                {
 04099                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04100                    mainFilters.Add(swDeintFilter);
 4101                }
 4102
 04103                var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
 04104                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 4105                // sw scale
 04106                mainFilters.Add(swScaleFilter);
 04107                mainFilters.Add($"format={outFormat}");
 4108
 4109                // keep video at memory except ocl tonemap,
 4110                // since the overhead caused by hwupload >>> using sw filter.
 4111                // sw => hw
 04112                if (doOclTonemap)
 4113                {
 04114                    mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=24");
 04115                    mainFilters.Add("format=d3d11");
 04116                    mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4117                }
 4118            }
 4119
 04120            if (isD3d11vaDecoder)
 4121            {
 4122                // INPUT d3d11 surface(vram)
 4123                // map from d3d11va to opencl via d3d11-opencl interop.
 04124                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4125
 4126                // hw deint <= TODO: finish the 'yadif_opencl' filter
 4127
 4128                // hw transpose
 04129                if (doOclTranspose)
 4130                {
 04131                    mainFilters.Add($"transpose_opencl=dir={transposeDir}");
 4132                }
 4133
 04134                var outFormat = doOclTonemap ? string.Empty : "nv12";
 04135                var hwScaleFilter = GetHwScaleFilter("scale", "opencl", outFormat, false, swpInW, swpInH, reqW, reqH, re
 4136                // hw scale
 04137                mainFilters.Add(hwScaleFilter);
 4138            }
 4139
 4140            // hw tonemap
 04141            if (doOclTonemap)
 4142            {
 04143                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04144                mainFilters.Add(tonemapFilter);
 4145            }
 4146
 04147            var memoryOutput = false;
 04148            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04149            if (isD3d11vaDecoder && isSwEncoder)
 4150            {
 04151                memoryOutput = true;
 4152
 4153                // OUTPUT nv12 surface(memory)
 4154                // prefer hwmap to hwdownload on opencl.
 04155                var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap=mode=read";
 04156                mainFilters.Add(hwTransferFilter);
 04157                mainFilters.Add("format=nv12");
 4158            }
 4159
 4160            // OUTPUT yuv420p surface
 04161            if (isSwDecoder && isAmfEncoder && !isUploadForOclTonemap)
 4162            {
 04163                memoryOutput = true;
 4164            }
 4165
 04166            if (memoryOutput)
 4167            {
 4168                // text subtitles
 04169                if (hasTextSubs)
 4170                {
 04171                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04172                    mainFilters.Add(textSubtitlesFilter);
 4173                }
 4174            }
 4175
 04176            if ((isDxInDxOut || isUploadForOclTonemap) && !hasSubs)
 4177            {
 4178                // OUTPUT d3d11(nv12) surface(vram)
 4179                // reverse-mapping via d3d11-opencl interop.
 04180                mainFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04181                mainFilters.Add("format=d3d11");
 4182            }
 4183
 4184            /* Make sub and overlay filters for subtitle stream */
 04185            var subFilters = new List<string>();
 04186            var overlayFilters = new List<string>();
 04187            if (isDxInDxOut || isUploadForOclTonemap)
 4188            {
 04189                if (hasSubs)
 4190                {
 04191                    var alphaFormatOpt = string.Empty;
 04192                    if (hasGraphicalSubs)
 4193                    {
 04194                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04195                        subFilters.Add(subPreProcFilters);
 04196                        subFilters.Add("format=yuva420p");
 4197                    }
 04198                    else if (hasTextSubs)
 4199                    {
 04200                        var framerate = state.VideoStream?.RealFrameRate;
 04201                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4202
 4203                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04204                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04205                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04206                        subFilters.Add(alphaSrcFilter);
 04207                        subFilters.Add("format=yuva420p");
 04208                        subFilters.Add(subTextSubtitlesFilter);
 4209
 04210                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclAlphaForma
 04211                            ? ":alpha_format=premultiplied" : string.Empty;
 4212                    }
 4213
 04214                    subFilters.Add("hwupload=derive_device=opencl");
 04215                    overlayFilters.Add($"overlay_opencl=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 04216                    overlayFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04217                    overlayFilters.Add("format=d3d11");
 4218                }
 4219            }
 04220            else if (memoryOutput)
 4221            {
 04222                if (hasGraphicalSubs)
 4223                {
 04224                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04225                    subFilters.Add(subPreProcFilters);
 04226                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4227                }
 4228            }
 4229
 04230            return (mainFilters, subFilters, overlayFilters);
 4231        }
 4232
 4233        /// <summary>
 4234        /// Gets the parameter of Intel QSV filter chain.
 4235        /// </summary>
 4236        /// <param name="state">Encoding state.</param>
 4237        /// <param name="options">Encoding options.</param>
 4238        /// <param name="vidEncoder">Video encoder to use.</param>
 4239        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4240        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVidFilterChain(
 4241            EncodingJobInfo state,
 4242            EncodingOptions options,
 4243            string vidEncoder)
 4244        {
 04245            if (options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 4246            {
 04247                return (null, null, null);
 4248            }
 4249
 04250            var isWindows = OperatingSystem.IsWindows();
 04251            var isLinux = OperatingSystem.IsLinux();
 04252            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04253            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04254            var isSwEncoder = !vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04255            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 04256            var isIntelDx11OclSupported = isWindows
 04257                && _mediaEncoder.SupportsHwaccel("d3d11va")
 04258                && isQsvOclSupported;
 04259            var isIntelVaapiOclSupported = isLinux
 04260                && IsVaapiSupported(state)
 04261                && isQsvOclSupported;
 4262
 4263            // legacy qsv pipeline(copy-back)
 04264            if ((isSwDecoder && isSwEncoder)
 04265                || (!isIntelVaapiOclSupported && !isIntelDx11OclSupported)
 04266                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4267            {
 04268                return GetSwVidFilterChain(state, options, vidEncoder);
 4269            }
 4270
 4271            // preferred qsv(vaapi) + opencl filters pipeline
 04272            if (isIntelVaapiOclSupported)
 4273            {
 04274                return GetIntelQsvVaapiVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4275            }
 4276
 4277            // preferred qsv(d3d11) + opencl filters pipeline
 04278            if (isIntelDx11OclSupported)
 4279            {
 04280                return GetIntelQsvDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4281            }
 4282
 04283            return (null, null, null);
 4284        }
 4285
 4286        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvDx11VidFilter
 4287            EncodingJobInfo state,
 4288            EncodingOptions options,
 4289            string vidDecoder,
 4290            string vidEncoder)
 4291        {
 04292            var inW = state.VideoStream?.Width;
 04293            var inH = state.VideoStream?.Height;
 04294            var reqW = state.BaseRequest.Width;
 04295            var reqH = state.BaseRequest.Height;
 04296            var reqMaxW = state.BaseRequest.MaxWidth;
 04297            var reqMaxH = state.BaseRequest.MaxHeight;
 04298            var threeDFormat = state.MediaSource.Video3DFormat;
 4299
 04300            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04301            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04302            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04303            var isHwDecoder = isD3d11vaDecoder || isQsvDecoder;
 04304            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04305            var isSwEncoder = !isQsvEncoder;
 04306            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04307            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4308
 04309            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04310            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04311            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04312            var doVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04313            var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options);
 04314            var doTonemap = doVppTonemap || doOclTonemap;
 4315
 04316            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04317            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04318            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04319            var hasAssSubs = hasSubs
 04320                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04321                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04322            var subW = state.SubtitleStream?.Width;
 04323            var subH = state.SubtitleStream?.Height;
 4324
 04325            var rotation = state.VideoStream?.Rotation ?? 0;
 04326            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04327            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04328            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isD3d11vaDecoder || isQsvDecoder) && doVppTran
 04329            var swpInW = swapWAndH ? inH : inW;
 04330            var swpInH = swapWAndH ? inW : inH;
 4331
 4332            /* Make main filters for video stream */
 04333            var mainFilters = new List<string>();
 4334
 04335            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4336
 04337            if (isSwDecoder)
 4338            {
 4339                // INPUT sw surface(memory)
 4340                // sw deint
 04341                if (doDeintH2645)
 4342                {
 04343                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04344                    mainFilters.Add(swDeintFilter);
 4345                }
 4346
 04347                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04348                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04349                if (isMjpegEncoder && !doOclTonemap)
 4350                {
 4351                    // sw decoder + hw mjpeg encoder
 04352                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4353                }
 4354
 4355                // sw scale
 04356                mainFilters.Add(swScaleFilter);
 04357                mainFilters.Add($"format={outFormat}");
 4358
 4359                // keep video at memory except ocl tonemap,
 4360                // since the overhead caused by hwupload >>> using sw filter.
 4361                // sw => hw
 04362                if (doOclTonemap)
 4363                {
 04364                    mainFilters.Add("hwupload=derive_device=opencl");
 4365                }
 4366            }
 04367            else if (isD3d11vaDecoder || isQsvDecoder)
 4368            {
 04369                var isRext = IsVideoStreamHevcRext(state);
 04370                var twoPassVppTonemap = false;
 04371                var doVppFullRangeOut = isMjpegEncoder
 04372                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04373                var doVppScaleModeHq = isMjpegEncoder
 04374                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 04375                var doVppProcamp = false;
 04376                var procampParams = string.Empty;
 04377                var procampParamsString = string.Empty;
 04378                if (doVppTonemap)
 4379                {
 04380                    if (isRext)
 4381                    {
 4382                        // VPP tonemap requires p010 input
 04383                        twoPassVppTonemap = true;
 4384                    }
 4385
 04386                    if (options.VppTonemappingBrightness != 0
 04387                        && options.VppTonemappingBrightness >= -100
 04388                        && options.VppTonemappingBrightness <= 100)
 4389                    {
 04390                        procampParamsString += ":brightness={0}";
 04391                        twoPassVppTonemap = doVppProcamp = true;
 4392                    }
 4393
 04394                    if (options.VppTonemappingContrast > 1
 04395                        && options.VppTonemappingContrast <= 10)
 4396                    {
 04397                        procampParamsString += ":contrast={1}";
 04398                        twoPassVppTonemap = doVppProcamp = true;
 4399                    }
 4400
 04401                    if (doVppProcamp)
 4402                    {
 04403                        procampParamsString += ":procamp=1:async_depth=2";
 04404                        procampParams = string.Format(
 04405                            CultureInfo.InvariantCulture,
 04406                            procampParamsString,
 04407                            options.VppTonemappingBrightness,
 04408                            options.VppTonemappingContrast);
 4409                    }
 4410                }
 4411
 04412                var outFormat = doOclTonemap ? ((doVppTranspose || isRext) ? "p010" : string.Empty) : "nv12";
 04413                outFormat = twoPassVppTonemap ? "p010" : outFormat;
 4414
 04415                var swapOutputWandH = doVppTranspose && swapWAndH;
 04416                var hwScaleFilter = GetHwScaleFilter("vpp", "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, req
 4417
 04418                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose)
 4419                {
 04420                    hwScaleFilter += $":transpose={transposeDir}";
 4421                }
 4422
 04423                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4424                {
 04425                    hwScaleFilter += (doVppFullRangeOut && !doOclTonemap) ? ":out_range=pc" : string.Empty;
 04426                    hwScaleFilter += doVppScaleModeHq ? ":scale_mode=hq" : string.Empty;
 4427                }
 4428
 04429                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTonemap)
 4430                {
 04431                    hwScaleFilter += doVppProcamp ? procampParams : (twoPassVppTonemap ? string.Empty : ":tonemap=1");
 4432                }
 4433
 04434                if (isD3d11vaDecoder)
 4435                {
 04436                    if (!string.IsNullOrEmpty(hwScaleFilter) || doDeintH2645)
 4437                    {
 4438                        // INPUT d3d11 surface(vram)
 4439                        // map from d3d11va to qsv.
 04440                        mainFilters.Add("hwmap=derive_device=qsv");
 4441                    }
 4442                }
 4443
 4444                // hw deint
 04445                if (doDeintH2645)
 4446                {
 04447                    var deintFilter = GetHwDeinterlaceFilter(state, options, "qsv");
 04448                    mainFilters.Add(deintFilter);
 4449                }
 4450
 4451                // hw transpose & scale & tonemap(w/o procamp)
 04452                mainFilters.Add(hwScaleFilter);
 4453
 4454                // hw tonemap(w/ procamp)
 04455                if (doVppTonemap && twoPassVppTonemap)
 4456                {
 04457                    mainFilters.Add("vpp_qsv=tonemap=1:format=nv12:async_depth=2");
 4458                }
 4459
 4460                // force bt709 just in case vpp tonemap is not triggered or using MSDK instead of VPL.
 04461                if (doVppTonemap)
 4462                {
 04463                    mainFilters.Add(GetOverwriteColorPropertiesParam(state, false));
 4464                }
 4465            }
 4466
 04467            if (doOclTonemap && isHwDecoder)
 4468            {
 4469                // map from qsv to opencl via qsv(d3d11)-opencl interop.
 04470                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4471            }
 4472
 4473            // hw tonemap
 04474            if (doOclTonemap)
 4475            {
 04476                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04477                mainFilters.Add(tonemapFilter);
 4478            }
 4479
 04480            var memoryOutput = false;
 04481            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04482            var isHwmapUsable = isSwEncoder && doOclTonemap;
 04483            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4484            {
 04485                memoryOutput = true;
 4486
 4487                // OUTPUT nv12 surface(memory)
 4488                // prefer hwmap to hwdownload on opencl.
 4489                // qsv hwmap is not fully implemented for the time being.
 04490                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04491                mainFilters.Add("format=nv12");
 4492            }
 4493
 4494            // OUTPUT nv12 surface(memory)
 04495            if (isSwDecoder && isQsvEncoder)
 4496            {
 04497                memoryOutput = true;
 4498            }
 4499
 04500            if (memoryOutput)
 4501            {
 4502                // text subtitles
 04503                if (hasTextSubs)
 4504                {
 04505                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04506                    mainFilters.Add(textSubtitlesFilter);
 4507                }
 4508            }
 4509
 04510            if (isQsvInQsvOut && doOclTonemap)
 4511            {
 4512                // OUTPUT qsv(nv12) surface(vram)
 4513                // reverse-mapping via qsv(d3d11)-opencl interop.
 04514                mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1");
 04515                mainFilters.Add("format=qsv");
 4516            }
 4517
 4518            /* Make sub and overlay filters for subtitle stream */
 04519            var subFilters = new List<string>();
 04520            var overlayFilters = new List<string>();
 04521            if (isQsvInQsvOut)
 4522            {
 04523                if (hasSubs)
 4524                {
 04525                    if (hasGraphicalSubs)
 4526                    {
 4527                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04528                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04529                        subFilters.Add(subPreProcFilters);
 04530                        subFilters.Add("format=bgra");
 4531                    }
 04532                    else if (hasTextSubs)
 4533                    {
 04534                        var framerate = state.VideoStream?.RealFrameRate;
 04535                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4536
 4537                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 04538                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04539                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04540                        subFilters.Add(alphaSrcFilter);
 04541                        subFilters.Add("format=bgra");
 04542                        subFilters.Add(subTextSubtitlesFilter);
 4543                    }
 4544
 4545                    // qsv requires a fixed pool size.
 4546                    // default to 64 otherwise it will fail on certain iGPU.
 04547                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4548
 04549                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04550                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04551                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04552                        : string.Empty;
 04553                    var overlayQsvFilter = string.Format(
 04554                        CultureInfo.InvariantCulture,
 04555                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04556                        overlaySize);
 04557                    overlayFilters.Add(overlayQsvFilter);
 4558                }
 4559            }
 04560            else if (memoryOutput)
 4561            {
 04562                if (hasGraphicalSubs)
 4563                {
 04564                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04565                    subFilters.Add(subPreProcFilters);
 04566                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4567                }
 4568            }
 4569
 04570            return (mainFilters, subFilters, overlayFilters);
 4571        }
 4572
 4573        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvVaapiVidFilte
 4574            EncodingJobInfo state,
 4575            EncodingOptions options,
 4576            string vidDecoder,
 4577            string vidEncoder)
 4578        {
 04579            var inW = state.VideoStream?.Width;
 04580            var inH = state.VideoStream?.Height;
 04581            var reqW = state.BaseRequest.Width;
 04582            var reqH = state.BaseRequest.Height;
 04583            var reqMaxW = state.BaseRequest.MaxWidth;
 04584            var reqMaxH = state.BaseRequest.MaxHeight;
 04585            var threeDFormat = state.MediaSource.Video3DFormat;
 4586
 04587            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04588            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04589            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04590            var isHwDecoder = isVaapiDecoder || isQsvDecoder;
 04591            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04592            var isSwEncoder = !isQsvEncoder;
 04593            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04594            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4595
 04596            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04597            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04598            var doVaVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04599            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04600            var doTonemap = doVaVppTonemap || doOclTonemap;
 04601            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4602
 04603            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04604            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04605            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04606            var hasAssSubs = hasSubs
 04607                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04608                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04609            var subW = state.SubtitleStream?.Width;
 04610            var subH = state.SubtitleStream?.Height;
 4611
 04612            var rotation = state.VideoStream?.Rotation ?? 0;
 04613            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04614            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04615            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isVaapiDecoder || isQsvDecoder) && doVppTransp
 04616            var swpInW = swapWAndH ? inH : inW;
 04617            var swpInH = swapWAndH ? inW : inH;
 4618
 4619            /* Make main filters for video stream */
 04620            var mainFilters = new List<string>();
 4621
 04622            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4623
 04624            if (isSwDecoder)
 4625            {
 4626                // INPUT sw surface(memory)
 4627                // sw deint
 04628                if (doDeintH2645)
 4629                {
 04630                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04631                    mainFilters.Add(swDeintFilter);
 4632                }
 4633
 04634                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04635                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04636                if (isMjpegEncoder && !doOclTonemap)
 4637                {
 4638                    // sw decoder + hw mjpeg encoder
 04639                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4640                }
 4641
 4642                // sw scale
 04643                mainFilters.Add(swScaleFilter);
 04644                mainFilters.Add($"format={outFormat}");
 4645
 4646                // keep video at memory except ocl tonemap,
 4647                // since the overhead caused by hwupload >>> using sw filter.
 4648                // sw => hw
 04649                if (doOclTonemap)
 4650                {
 04651                    mainFilters.Add("hwupload=derive_device=opencl");
 4652                }
 4653            }
 04654            else if (isVaapiDecoder || isQsvDecoder)
 4655            {
 04656                var hwFilterSuffix = isVaapiDecoder ? "vaapi" : "qsv";
 04657                var isRext = IsVideoStreamHevcRext(state);
 04658                var doVppFullRangeOut = isMjpegEncoder
 04659                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04660                var doVppScaleModeHq = isMjpegEncoder
 04661                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 4662
 4663                // INPUT vaapi/qsv surface(vram)
 4664                // hw deint
 04665                if (doDeintH2645)
 4666                {
 04667                    var deintFilter = GetHwDeinterlaceFilter(state, options, hwFilterSuffix);
 04668                    mainFilters.Add(deintFilter);
 4669                }
 4670
 4671                // hw transpose(vaapi vpp)
 04672                if (isVaapiDecoder && doVppTranspose)
 4673                {
 04674                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 4675                }
 4676
 04677                var outFormat = doTonemap ? (((isQsvDecoder && doVppTranspose) || isRext) ? "p010" : string.Empty) : "nv
 04678                var swapOutputWandH = isQsvDecoder && doVppTranspose && swapWAndH;
 04679                var hwScalePrefix = isQsvDecoder ? "vpp" : "scale";
 04680                var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, hwFilterSuffix, outFormat, swapOutputWandH, swpInW, 
 4681
 04682                if (!string.IsNullOrEmpty(hwScaleFilter) && isQsvDecoder && doVppTranspose)
 4683                {
 04684                    hwScaleFilter += $":transpose={transposeDir}";
 4685                }
 4686
 04687                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4688                {
 04689                    hwScaleFilter += ((isQsvDecoder && !doVppFullRangeOut) || doOclTonemap) ? string.Empty : ":out_range
 04690                    hwScaleFilter += isQsvDecoder ? (doVppScaleModeHq ? ":scale_mode=hq" : string.Empty) : ":mode=hq";
 4691                }
 4692
 4693                // allocate extra pool sizes for vaapi vpp scale
 04694                if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder)
 4695                {
 04696                    hwScaleFilter += ":extra_hw_frames=24";
 4697                }
 4698
 4699                // hw transpose(qsv vpp) & scale
 04700                mainFilters.Add(hwScaleFilter);
 4701            }
 4702
 4703            // vaapi vpp tonemap
 04704            if (doVaVppTonemap && isHwDecoder)
 4705            {
 04706                if (isQsvDecoder)
 4707                {
 4708                    // map from qsv to vaapi.
 04709                    mainFilters.Add("hwmap=derive_device=vaapi");
 04710                    mainFilters.Add("format=vaapi");
 4711                }
 4712
 04713                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 04714                mainFilters.Add(tonemapFilter);
 4715
 04716                if (isQsvDecoder)
 4717                {
 4718                    // map from vaapi to qsv.
 04719                    mainFilters.Add("hwmap=derive_device=qsv");
 04720                    mainFilters.Add("format=qsv");
 4721                }
 4722            }
 4723
 04724            if (doOclTonemap && isHwDecoder)
 4725            {
 4726                // map from qsv to opencl via qsv(vaapi)-opencl interop.
 04727                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4728            }
 4729
 4730            // ocl tonemap
 04731            if (doOclTonemap)
 4732            {
 04733                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04734                mainFilters.Add(tonemapFilter);
 4735            }
 4736
 04737            var memoryOutput = false;
 04738            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04739            var isHwmapUsable = isSwEncoder && (doOclTonemap || isVaapiDecoder);
 04740            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4741            {
 04742                memoryOutput = true;
 4743
 4744                // OUTPUT nv12 surface(memory)
 4745                // prefer hwmap to hwdownload on opencl/vaapi.
 4746                // qsv hwmap is not fully implemented for the time being.
 04747                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04748                mainFilters.Add("format=nv12");
 4749            }
 4750
 4751            // OUTPUT nv12 surface(memory)
 04752            if (isSwDecoder && isQsvEncoder)
 4753            {
 04754                memoryOutput = true;
 4755            }
 4756
 04757            if (memoryOutput)
 4758            {
 4759                // text subtitles
 04760                if (hasTextSubs)
 4761                {
 04762                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04763                    mainFilters.Add(textSubtitlesFilter);
 4764                }
 4765            }
 4766
 04767            if (isQsvInQsvOut)
 4768            {
 04769                if (doOclTonemap)
 4770                {
 4771                    // OUTPUT qsv(nv12) surface(vram)
 4772                    // reverse-mapping via qsv(vaapi)-opencl interop.
 4773                    // add extra pool size to avoid the 'cannot allocate memory' error on hevc_qsv.
 04774                    mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1:extra_hw_frames=16");
 04775                    mainFilters.Add("format=qsv");
 4776                }
 04777                else if (isVaapiDecoder)
 4778                {
 04779                    mainFilters.Add("hwmap=derive_device=qsv");
 04780                    mainFilters.Add("format=qsv");
 4781                }
 4782            }
 4783
 4784            /* Make sub and overlay filters for subtitle stream */
 04785            var subFilters = new List<string>();
 04786            var overlayFilters = new List<string>();
 04787            if (isQsvInQsvOut)
 4788            {
 04789                if (hasSubs)
 4790                {
 04791                    if (hasGraphicalSubs)
 4792                    {
 4793                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04794                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04795                        subFilters.Add(subPreProcFilters);
 04796                        subFilters.Add("format=bgra");
 4797                    }
 04798                    else if (hasTextSubs)
 4799                    {
 04800                        var framerate = state.VideoStream?.RealFrameRate;
 04801                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4802
 04803                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04804                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04805                        subFilters.Add(alphaSrcFilter);
 04806                        subFilters.Add("format=bgra");
 04807                        subFilters.Add(subTextSubtitlesFilter);
 4808                    }
 4809
 4810                    // qsv requires a fixed pool size.
 4811                    // default to 64 otherwise it will fail on certain iGPU.
 04812                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4813
 04814                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04815                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04816                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04817                        : string.Empty;
 04818                    var overlayQsvFilter = string.Format(
 04819                        CultureInfo.InvariantCulture,
 04820                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04821                        overlaySize);
 04822                    overlayFilters.Add(overlayQsvFilter);
 4823                }
 4824            }
 04825            else if (memoryOutput)
 4826            {
 04827                if (hasGraphicalSubs)
 4828                {
 04829                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04830                    subFilters.Add(subPreProcFilters);
 04831                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4832                }
 4833            }
 4834
 04835            return (mainFilters, subFilters, overlayFilters);
 4836        }
 4837
 4838        /// <summary>
 4839        /// Gets the parameter of Intel/AMD VAAPI filter chain.
 4840        /// </summary>
 4841        /// <param name="state">Encoding state.</param>
 4842        /// <param name="options">Encoding options.</param>
 4843        /// <param name="vidEncoder">Video encoder to use.</param>
 4844        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4845        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiVidFilterChain(
 4846            EncodingJobInfo state,
 4847            EncodingOptions options,
 4848            string vidEncoder)
 4849        {
 04850            if (options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 4851            {
 04852                return (null, null, null);
 4853            }
 4854
 04855            var isLinux = OperatingSystem.IsLinux();
 04856            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04857            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04858            var isSwEncoder = !vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04859            var isVaapiFullSupported = isLinux && IsVaapiSupported(state) && IsVaapiFullSupported();
 04860            var isVaapiOclSupported = isVaapiFullSupported && IsOpenclFullSupported();
 04861            var isVaapiVkSupported = isVaapiFullSupported && IsVulkanFullSupported();
 4862
 4863            // legacy vaapi pipeline(copy-back)
 04864            if ((isSwDecoder && isSwEncoder)
 04865                || !isVaapiOclSupported
 04866                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4867            {
 04868                var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
 4869
 04870                if (!isSwEncoder)
 4871                {
 04872                    var newfilters = new List<string>();
 04873                    var noOverlay = swFilterChain.OverlayFilters.Count == 0;
 04874                    newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
 04875                    newfilters.Add("hwupload=derive_device=vaapi");
 4876
 04877                    var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
 04878                    var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
 04879                    return (mainFilters, swFilterChain.SubFilters, overlayFilters);
 4880                }
 4881
 04882                return swFilterChain;
 4883            }
 4884
 4885            // preferred vaapi + opencl filters pipeline
 04886            if (_mediaEncoder.IsVaapiDeviceInteliHD)
 4887            {
 4888                // Intel iHD path, with extra vpp tonemap and overlay support.
 04889                return GetIntelVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4890            }
 4891
 4892            // preferred vaapi + vulkan filters pipeline
 04893            if (_mediaEncoder.IsVaapiDeviceAmd
 04894                && isVaapiVkSupported
 04895                && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 04896                && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 4897            {
 4898                // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support.
 04899                return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4900            }
 4901
 4902            // Intel i965 and Amd legacy driver path, only featuring scale and deinterlace support.
 04903            return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4904        }
 4905
 4906        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVaapiFullVidFilt
 4907            EncodingJobInfo state,
 4908            EncodingOptions options,
 4909            string vidDecoder,
 4910            string vidEncoder)
 4911        {
 04912            var inW = state.VideoStream?.Width;
 04913            var inH = state.VideoStream?.Height;
 04914            var reqW = state.BaseRequest.Width;
 04915            var reqH = state.BaseRequest.Height;
 04916            var reqMaxW = state.BaseRequest.MaxWidth;
 04917            var reqMaxH = state.BaseRequest.MaxHeight;
 04918            var threeDFormat = state.MediaSource.Video3DFormat;
 4919
 04920            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04921            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04922            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04923            var isSwEncoder = !isVaapiEncoder;
 04924            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04925            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 4926
 04927            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04928            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04929            var doVaVppTonemap = isVaapiDecoder && IsIntelVppTonemapAvailable(state, options);
 04930            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04931            var doTonemap = doVaVppTonemap || doOclTonemap;
 04932            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4933
 04934            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04935            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04936            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04937            var hasAssSubs = hasSubs
 04938                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04939                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04940            var subW = state.SubtitleStream?.Width;
 04941            var subH = state.SubtitleStream?.Height;
 4942
 04943            var rotation = state.VideoStream?.Rotation ?? 0;
 04944            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04945            var doVaVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04946            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVaVppTranspose));
 04947            var swpInW = swapWAndH ? inH : inW;
 04948            var swpInH = swapWAndH ? inW : inH;
 4949
 4950            /* Make main filters for video stream */
 04951            var mainFilters = new List<string>();
 4952
 04953            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4954
 04955            if (isSwDecoder)
 4956            {
 4957                // INPUT sw surface(memory)
 4958                // sw deint
 04959                if (doDeintH2645)
 4960                {
 04961                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04962                    mainFilters.Add(swDeintFilter);
 4963                }
 4964
 04965                var outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 04966                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04967                if (isMjpegEncoder && !doOclTonemap)
 4968                {
 4969                    // sw decoder + hw mjpeg encoder
 04970                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4971                }
 4972
 4973                // sw scale
 04974                mainFilters.Add(swScaleFilter);
 04975                mainFilters.Add($"format={outFormat}");
 4976
 4977                // keep video at memory except ocl tonemap,
 4978                // since the overhead caused by hwupload >>> using sw filter.
 4979                // sw => hw
 04980                if (doOclTonemap)
 4981                {
 04982                    mainFilters.Add("hwupload=derive_device=opencl");
 4983                }
 4984            }
 04985            else if (isVaapiDecoder)
 4986            {
 04987                var isRext = IsVideoStreamHevcRext(state);
 4988
 4989                // INPUT vaapi surface(vram)
 4990                // hw deint
 04991                if (doDeintH2645)
 4992                {
 04993                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 04994                    mainFilters.Add(deintFilter);
 4995                }
 4996
 4997                // hw transpose
 04998                if (doVaVppTranspose)
 4999                {
 05000                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 5001                }
 5002
 05003                var outFormat = doTonemap ? (isRext ? "p010" : string.Empty) : "nv12";
 05004                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, swpInW, swpInH, reqW, reqH, req
 5005
 05006                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5007                {
 05008                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05009                    hwScaleFilter += ":mode=hq";
 5010                }
 5011
 5012                // allocate extra pool sizes for vaapi vpp
 05013                if (!string.IsNullOrEmpty(hwScaleFilter))
 5014                {
 05015                    hwScaleFilter += ":extra_hw_frames=24";
 5016                }
 5017
 5018                // hw scale
 05019                mainFilters.Add(hwScaleFilter);
 5020            }
 5021
 5022            // vaapi vpp tonemap
 05023            if (doVaVppTonemap && isVaapiDecoder)
 5024            {
 05025                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 05026                mainFilters.Add(tonemapFilter);
 5027            }
 5028
 05029            if (doOclTonemap && isVaapiDecoder)
 5030            {
 5031                // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05032                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 5033            }
 5034
 5035            // ocl tonemap
 05036            if (doOclTonemap)
 5037            {
 05038                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05039                mainFilters.Add(tonemapFilter);
 5040            }
 5041
 05042            if (doOclTonemap && isVaInVaOut)
 5043            {
 5044                // OUTPUT vaapi(nv12) surface(vram)
 5045                // reverse-mapping via vaapi-opencl interop.
 05046                mainFilters.Add("hwmap=derive_device=vaapi:mode=write:reverse=1");
 05047                mainFilters.Add("format=vaapi");
 5048            }
 5049
 05050            var memoryOutput = false;
 05051            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 05052            var isHwmapNotUsable = isUploadForOclTonemap && isVaapiEncoder;
 05053            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap)
 5054            {
 05055                memoryOutput = true;
 5056
 5057                // OUTPUT nv12 surface(memory)
 5058                // prefer hwmap to hwdownload on opencl/vaapi.
 05059                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap=mode=read");
 05060                mainFilters.Add("format=nv12");
 5061            }
 5062
 5063            // OUTPUT nv12 surface(memory)
 05064            if (isSwDecoder && isVaapiEncoder)
 5065            {
 05066                memoryOutput = true;
 5067            }
 5068
 05069            if (memoryOutput)
 5070            {
 5071                // text subtitles
 05072                if (hasTextSubs)
 5073                {
 05074                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05075                    mainFilters.Add(textSubtitlesFilter);
 5076                }
 5077            }
 5078
 05079            if (memoryOutput && isVaapiEncoder)
 5080            {
 05081                if (!hasGraphicalSubs)
 5082                {
 05083                    mainFilters.Add("hwupload_vaapi");
 5084                }
 5085            }
 5086
 5087            /* Make sub and overlay filters for subtitle stream */
 05088            var subFilters = new List<string>();
 05089            var overlayFilters = new List<string>();
 05090            if (isVaInVaOut)
 5091            {
 05092                if (hasSubs)
 5093                {
 05094                    if (hasGraphicalSubs)
 5095                    {
 5096                        // overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead
 05097                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 05098                        subFilters.Add(subPreProcFilters);
 05099                        subFilters.Add("format=bgra");
 5100                    }
 05101                    else if (hasTextSubs)
 5102                    {
 05103                        var framerate = state.VideoStream?.RealFrameRate;
 05104                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5105
 05106                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 05107                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05108                        subFilters.Add(alphaSrcFilter);
 05109                        subFilters.Add("format=bgra");
 05110                        subFilters.Add(subTextSubtitlesFilter);
 5111                    }
 5112
 05113                    subFilters.Add("hwupload=derive_device=vaapi");
 5114
 05115                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 05116                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 05117                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 05118                        : string.Empty;
 05119                    var overlayVaapiFilter = string.Format(
 05120                        CultureInfo.InvariantCulture,
 05121                        "overlay_vaapi=eof_action=pass:repeatlast=0{0}",
 05122                        overlaySize);
 05123                    overlayFilters.Add(overlayVaapiFilter);
 5124                }
 5125            }
 05126            else if (memoryOutput)
 5127            {
 05128                if (hasGraphicalSubs)
 5129                {
 05130                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05131                    subFilters.Add(subPreProcFilters);
 05132                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5133
 05134                    if (isVaapiEncoder)
 5135                    {
 05136                        overlayFilters.Add("hwupload_vaapi");
 5137                    }
 5138                }
 5139            }
 5140
 05141            return (mainFilters, subFilters, overlayFilters);
 5142        }
 5143
 5144        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVaapiFullVidFilter
 5145            EncodingJobInfo state,
 5146            EncodingOptions options,
 5147            string vidDecoder,
 5148            string vidEncoder)
 5149        {
 05150            var inW = state.VideoStream?.Width;
 05151            var inH = state.VideoStream?.Height;
 05152            var reqW = state.BaseRequest.Width;
 05153            var reqH = state.BaseRequest.Height;
 05154            var reqMaxW = state.BaseRequest.MaxWidth;
 05155            var reqMaxH = state.BaseRequest.MaxHeight;
 05156            var threeDFormat = state.MediaSource.Video3DFormat;
 5157
 05158            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05159            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05160            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05161            var isSwEncoder = !isVaapiEncoder;
 05162            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5163
 05164            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05165            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05166            var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
 05167            var doDeintH2645 = doDeintH264 || doDeintHevc;
 5168
 05169            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05170            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05171            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05172            var hasAssSubs = hasSubs
 05173                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05174                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5175
 05176            var rotation = state.VideoStream?.Rotation ?? 0;
 05177            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05178            var doVkTranspose = isVaapiDecoder && !string.IsNullOrEmpty(transposeDir);
 05179            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVkTranspose));
 05180            var swpInW = swapWAndH ? inH : inW;
 05181            var swpInH = swapWAndH ? inW : inH;
 5182
 5183            /* Make main filters for video stream */
 05184            var mainFilters = new List<string>();
 5185
 05186            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doVkTonemap));
 5187
 05188            if (isSwDecoder)
 5189            {
 5190                // INPUT sw surface(memory)
 5191                // sw deint
 05192                if (doDeintH2645)
 5193                {
 05194                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05195                    mainFilters.Add(swDeintFilter);
 5196                }
 5197
 05198                if (doVkTonemap || hasSubs)
 5199                {
 5200                    // sw => hw
 05201                    mainFilters.Add("hwupload=derive_device=vulkan");
 05202                    mainFilters.Add("format=vulkan");
 5203                }
 5204                else
 5205                {
 5206                    // sw scale
 05207                    var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW,
 05208                    mainFilters.Add(swScaleFilter);
 05209                    mainFilters.Add("format=nv12");
 5210                }
 5211            }
 05212            else if (isVaapiDecoder)
 5213            {
 5214                // INPUT vaapi surface(vram)
 05215                if (doVkTranspose || doVkTonemap || hasSubs)
 5216                {
 5217                    // map from vaapi to vulkan/drm via interop (Polaris/gfx8+).
 05218                    if (_mediaEncoder.EncoderVersion >= _minFFmpegAlteredVaVkInterop)
 5219                    {
 05220                        if (doVkTranspose || !_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5221                        {
 5222                            // disable the indirect va-drm-vk mapping since it's no longer reliable.
 05223                            mainFilters.Add("hwmap=derive_device=drm");
 05224                            mainFilters.Add("format=drm_prime");
 05225                            mainFilters.Add("hwmap=derive_device=vulkan");
 05226                            mainFilters.Add("format=vulkan");
 5227
 5228                            // workaround for libplacebo using the imported vulkan frame on gfx8.
 05229                            if (!_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5230                            {
 05231                                mainFilters.Add("scale_vulkan");
 5232                            }
 5233                        }
 05234                        else if (doVkTonemap || hasSubs)
 5235                        {
 5236                            // non ad-hoc libplacebo also accepts drm_prime direct input.
 05237                            mainFilters.Add("hwmap=derive_device=drm");
 05238                            mainFilters.Add("format=drm_prime");
 5239                        }
 5240                    }
 5241                    else // legacy va-vk mapping that works only in jellyfin-ffmpeg6
 5242                    {
 05243                        mainFilters.Add("hwmap=derive_device=vulkan");
 05244                        mainFilters.Add("format=vulkan");
 5245                    }
 5246                }
 5247                else
 5248                {
 5249                    // hw deint
 05250                    if (doDeintH2645)
 5251                    {
 05252                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05253                        mainFilters.Add(deintFilter);
 5254                    }
 5255
 5256                    // hw scale
 05257                    var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", "nv12", false, inW, inH, reqW, reqH, reqMaxW,
 5258
 05259                    if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder && !doVkTonemap)
 5260                    {
 05261                        hwScaleFilter += ":out_range=pc:mode=hq";
 5262                    }
 5263
 05264                    mainFilters.Add(hwScaleFilter);
 5265                }
 5266            }
 5267
 5268            // vk transpose
 05269            if (doVkTranspose)
 5270            {
 05271                if (string.Equals(transposeDir, "reversal", StringComparison.OrdinalIgnoreCase))
 5272                {
 05273                    mainFilters.Add("flip_vulkan");
 5274                }
 5275                else
 5276                {
 05277                    mainFilters.Add($"transpose_vulkan=dir={transposeDir}");
 5278                }
 5279            }
 5280
 5281            // vk libplacebo
 05282            if (doVkTonemap || hasSubs)
 5283            {
 05284                var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, req
 05285                mainFilters.Add(libplaceboFilter);
 05286                mainFilters.Add("format=vulkan");
 5287            }
 5288
 05289            if (doVkTonemap && !hasSubs)
 5290            {
 5291                // OUTPUT vaapi(nv12) surface(vram)
 5292                // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05293                mainFilters.Add("hwmap=derive_device=vaapi");
 05294                mainFilters.Add("format=vaapi");
 5295
 5296                // clear the surf->meta_offset and output nv12
 05297                mainFilters.Add("scale_vaapi=format=nv12");
 5298
 5299                // hw deint
 05300                if (doDeintH2645)
 5301                {
 05302                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05303                    mainFilters.Add(deintFilter);
 5304                }
 5305            }
 5306
 05307            if (!hasSubs)
 5308            {
 5309                // OUTPUT nv12 surface(memory)
 05310                if (isSwEncoder && (doVkTonemap || isVaapiDecoder))
 5311                {
 05312                    mainFilters.Add("hwdownload");
 05313                    mainFilters.Add("format=nv12");
 5314                }
 5315
 05316                if (isSwDecoder && isVaapiEncoder && !doVkTonemap)
 5317                {
 05318                    mainFilters.Add("hwupload_vaapi");
 5319                }
 5320            }
 5321
 5322            /* Make sub and overlay filters for subtitle stream */
 05323            var subFilters = new List<string>();
 05324            var overlayFilters = new List<string>();
 05325            if (hasSubs)
 5326            {
 05327                if (hasGraphicalSubs)
 5328                {
 05329                    var subW = state.SubtitleStream?.Width;
 05330                    var subH = state.SubtitleStream?.Height;
 05331                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05332                    subFilters.Add(subPreProcFilters);
 05333                    subFilters.Add("format=bgra");
 5334                }
 05335                else if (hasTextSubs)
 5336                {
 05337                    var framerate = state.VideoStream?.RealFrameRate;
 05338                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5339
 05340                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05341                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05342                    subFilters.Add(alphaSrcFilter);
 05343                    subFilters.Add("format=bgra");
 05344                    subFilters.Add(subTextSubtitlesFilter);
 5345                }
 5346
 05347                subFilters.Add("hwupload=derive_device=vulkan");
 05348                subFilters.Add("format=vulkan");
 5349
 05350                overlayFilters.Add("overlay_vulkan=eof_action=pass:repeatlast=0");
 5351
 05352                if (isSwEncoder)
 5353                {
 5354                    // OUTPUT nv12 surface(memory)
 05355                    overlayFilters.Add("scale_vulkan=format=nv12");
 05356                    overlayFilters.Add("hwdownload");
 05357                    overlayFilters.Add("format=nv12");
 5358                }
 05359                else if (isVaapiEncoder)
 5360                {
 5361                    // OUTPUT vaapi(nv12) surface(vram)
 5362                    // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05363                    overlayFilters.Add("hwmap=derive_device=vaapi");
 05364                    overlayFilters.Add("format=vaapi");
 5365
 5366                    // clear the surf->meta_offset and output nv12
 05367                    overlayFilters.Add("scale_vaapi=format=nv12");
 5368
 5369                    // hw deint
 05370                    if (doDeintH2645)
 5371                    {
 05372                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05373                        overlayFilters.Add(deintFilter);
 5374                    }
 5375                }
 5376            }
 5377
 05378            return (mainFilters, subFilters, overlayFilters);
 5379        }
 5380
 5381        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiLimitedVidFilter
 5382            EncodingJobInfo state,
 5383            EncodingOptions options,
 5384            string vidDecoder,
 5385            string vidEncoder)
 5386        {
 05387            var inW = state.VideoStream?.Width;
 05388            var inH = state.VideoStream?.Height;
 05389            var reqW = state.BaseRequest.Width;
 05390            var reqH = state.BaseRequest.Height;
 05391            var reqMaxW = state.BaseRequest.MaxWidth;
 05392            var reqMaxH = state.BaseRequest.MaxHeight;
 05393            var threeDFormat = state.MediaSource.Video3DFormat;
 5394
 05395            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05396            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05397            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05398            var isSwEncoder = !isVaapiEncoder;
 05399            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05400            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 05401            var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965;
 05402            var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd;
 5403
 05404            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05405            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05406            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05407            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5408
 05409            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05410            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05411            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 5412
 05413            var rotation = state.VideoStream?.Rotation ?? 0;
 05414            var swapWAndH = Math.Abs(rotation) == 90 && isSwDecoder;
 05415            var swpInW = swapWAndH ? inH : inW;
 05416            var swpInH = swapWAndH ? inW : inH;
 5417
 5418            /* Make main filters for video stream */
 05419            var mainFilters = new List<string>();
 5420
 05421            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5422
 05423            var outFormat = string.Empty;
 05424            if (isSwDecoder)
 5425            {
 5426                // INPUT sw surface(memory)
 5427                // sw deint
 05428                if (doDeintH2645)
 5429                {
 05430                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05431                    mainFilters.Add(swDeintFilter);
 5432                }
 5433
 05434                outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05435                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05436                if (isMjpegEncoder && !doOclTonemap)
 5437                {
 5438                    // sw decoder + hw mjpeg encoder
 05439                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5440                }
 5441
 5442                // sw scale
 05443                mainFilters.Add(swScaleFilter);
 05444                mainFilters.Add("format=" + outFormat);
 5445
 5446                // keep video at memory except ocl tonemap,
 5447                // since the overhead caused by hwupload >>> using sw filter.
 5448                // sw => hw
 05449                if (doOclTonemap)
 5450                {
 05451                    mainFilters.Add("hwupload=derive_device=opencl");
 5452                }
 5453            }
 05454            else if (isVaapiDecoder)
 5455            {
 5456                // INPUT vaapi surface(vram)
 5457                // hw deint
 05458                if (doDeintH2645)
 5459                {
 05460                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05461                    mainFilters.Add(deintFilter);
 5462                }
 5463
 05464                outFormat = doOclTonemap ? string.Empty : "nv12";
 05465                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, inW, inH, reqW, reqH, reqMaxW, 
 5466
 05467                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5468                {
 05469                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05470                    hwScaleFilter += ":mode=hq";
 5471                }
 5472
 5473                // allocate extra pool sizes for vaapi vpp
 05474                if (!string.IsNullOrEmpty(hwScaleFilter))
 5475                {
 05476                    hwScaleFilter += ":extra_hw_frames=24";
 5477                }
 5478
 5479                // hw scale
 05480                mainFilters.Add(hwScaleFilter);
 5481            }
 5482
 05483            if (doOclTonemap && isVaapiDecoder)
 5484            {
 05485                if (isi965Driver)
 5486                {
 5487                    // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05488                    mainFilters.Add("hwmap=derive_device=opencl");
 5489                }
 5490                else
 5491                {
 05492                    mainFilters.Add("hwdownload");
 05493                    mainFilters.Add("format=p010le");
 05494                    mainFilters.Add("hwupload=derive_device=opencl");
 5495                }
 5496            }
 5497
 5498            // ocl tonemap
 05499            if (doOclTonemap)
 5500            {
 05501                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05502                mainFilters.Add(tonemapFilter);
 5503            }
 5504
 05505            if (doOclTonemap && isVaInVaOut)
 5506            {
 05507                if (isi965Driver)
 5508                {
 5509                    // OUTPUT vaapi(nv12) surface(vram)
 5510                    // reverse-mapping via vaapi-opencl interop.
 05511                    mainFilters.Add("hwmap=derive_device=vaapi:reverse=1");
 05512                    mainFilters.Add("format=vaapi");
 5513                }
 5514            }
 5515
 05516            var memoryOutput = false;
 05517            var isUploadForOclTonemap = doOclTonemap && (isSwDecoder || (isVaapiDecoder && !isi965Driver));
 05518            var isHwmapNotUsable = hasGraphicalSubs || isUploadForOclTonemap;
 05519            var isHwmapForSubs = hasSubs && isVaapiDecoder;
 05520            var isHwUnmapForTextSubs = hasTextSubs && isVaInVaOut && !isUploadForOclTonemap;
 05521            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap || isHwmapForSubs)
 5522            {
 05523                memoryOutput = true;
 5524
 5525                // OUTPUT nv12 surface(memory)
 5526                // prefer hwmap to hwdownload on opencl/vaapi.
 05527                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap");
 05528                mainFilters.Add("format=nv12");
 5529            }
 5530
 5531            // OUTPUT nv12 surface(memory)
 05532            if (isSwDecoder && isVaapiEncoder)
 5533            {
 05534                memoryOutput = true;
 5535            }
 5536
 05537            if (memoryOutput)
 5538            {
 5539                // text subtitles
 05540                if (hasTextSubs)
 5541                {
 05542                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05543                    mainFilters.Add(textSubtitlesFilter);
 5544                }
 5545            }
 5546
 05547            if (isHwUnmapForTextSubs)
 5548            {
 05549                mainFilters.Add("hwmap");
 05550                mainFilters.Add("format=vaapi");
 5551            }
 05552            else if (memoryOutput && isVaapiEncoder)
 5553            {
 05554                if (!hasGraphicalSubs)
 5555                {
 05556                    mainFilters.Add("hwupload_vaapi");
 5557                }
 5558            }
 5559
 5560            /* Make sub and overlay filters for subtitle stream */
 05561            var subFilters = new List<string>();
 05562            var overlayFilters = new List<string>();
 05563            if (memoryOutput)
 5564            {
 05565                if (hasGraphicalSubs)
 5566                {
 05567                    var subW = state.SubtitleStream?.Width;
 05568                    var subH = state.SubtitleStream?.Height;
 05569                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05570                    subFilters.Add(subPreProcFilters);
 05571                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5572
 05573                    if (isVaapiEncoder)
 5574                    {
 05575                        overlayFilters.Add("hwupload_vaapi");
 5576                    }
 5577                }
 5578            }
 5579
 05580            return (mainFilters, subFilters, overlayFilters);
 5581        }
 5582
 5583        /// <summary>
 5584        /// Gets the parameter of Apple VideoToolBox filter chain.
 5585        /// </summary>
 5586        /// <param name="state">Encoding state.</param>
 5587        /// <param name="options">Encoding options.</param>
 5588        /// <param name="vidEncoder">Video encoder to use.</param>
 5589        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5590        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFilterChain(
 5591            EncodingJobInfo state,
 5592            EncodingOptions options,
 5593            string vidEncoder)
 5594        {
 05595            if (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 5596            {
 05597                return (null, null, null);
 5598            }
 5599
 5600            // ReSharper disable once InconsistentNaming
 05601            var isMacOS = OperatingSystem.IsMacOS();
 05602            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05603            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05604            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05605            var isVtFullSupported = isMacOS && IsVideoToolboxFullSupported();
 5606
 5607            // legacy videotoolbox pipeline (disable hw filters)
 05608            if (!(isVtEncoder || isVtDecoder)
 05609                || !isVtFullSupported
 05610                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5611            {
 05612                return GetSwVidFilterChain(state, options, vidEncoder);
 5613            }
 5614
 5615            // preferred videotoolbox + metal filters pipeline
 05616            return GetAppleVidFiltersPreferred(state, options, vidDecoder, vidEncoder);
 5617        }
 5618
 5619        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFiltersPrefer
 5620            EncodingJobInfo state,
 5621            EncodingOptions options,
 5622            string vidDecoder,
 5623            string vidEncoder)
 5624        {
 05625            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05626            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05627            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5628
 05629            var inW = state.VideoStream?.Width;
 05630            var inH = state.VideoStream?.Height;
 05631            var reqW = state.BaseRequest.Width;
 05632            var reqH = state.BaseRequest.Height;
 05633            var reqMaxW = state.BaseRequest.MaxWidth;
 05634            var reqMaxH = state.BaseRequest.MaxHeight;
 05635            var threeDFormat = state.MediaSource.Video3DFormat;
 5636
 05637            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05638            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05639            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05640            var doVtTonemap = IsVideoToolboxTonemapAvailable(state, options);
 05641            var doMetalTonemap = !doVtTonemap && IsHwTonemapAvailable(state, options);
 05642            var usingHwSurface = isVtDecoder && (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface);
 5643
 05644            var rotation = state.VideoStream?.Rotation ?? 0;
 05645            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05646            var doVtTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_vt");
 05647            var swapWAndH = Math.Abs(rotation) == 90 && doVtTranspose;
 05648            var swpInW = swapWAndH ? inH : inW;
 05649            var swpInH = swapWAndH ? inW : inH;
 5650
 05651            var scaleFormat = string.Empty;
 5652            // Use P010 for Metal tone mapping, otherwise force an 8bit output.
 05653            if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase))
 5654            {
 05655                if (doMetalTonemap)
 5656                {
 05657                    if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 5658                    {
 05659                        scaleFormat = "p010le";
 5660                    }
 5661                }
 5662                else
 5663                {
 05664                    scaleFormat = "nv12";
 5665                }
 5666            }
 5667
 05668            var hwScaleFilter = GetHwScaleFilter("scale", "vt", scaleFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW,
 5669
 05670            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05671            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05672            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05673            var hasAssSubs = hasSubs
 05674                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05675                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5676
 5677            /* Make main filters for video stream */
 05678            var mainFilters = new List<string>();
 5679
 5680            // hw deint
 05681            if (doDeintH2645)
 5682            {
 05683                var deintFilter = GetHwDeinterlaceFilter(state, options, "videotoolbox");
 05684                mainFilters.Add(deintFilter);
 5685            }
 5686
 5687            // hw transpose
 05688            if (doVtTranspose)
 5689            {
 05690                mainFilters.Add($"transpose_vt=dir={transposeDir}");
 5691            }
 5692
 05693            if (doVtTonemap)
 5694            {
 5695                const string VtTonemapArgs = "color_matrix=bt709:color_primaries=bt709:color_transfer=bt709";
 5696
 5697                // scale_vt can handle scaling & tonemapping in one shot, just like vpp_qsv.
 05698                hwScaleFilter = string.IsNullOrEmpty(hwScaleFilter)
 05699                    ? "scale_vt=" + VtTonemapArgs
 05700                    : hwScaleFilter + ":" + VtTonemapArgs;
 5701            }
 5702
 5703            // hw scale & vt tonemap
 05704            mainFilters.Add(hwScaleFilter);
 5705
 5706            // Metal tonemap
 05707            if (doMetalTonemap)
 5708            {
 05709                var tonemapFilter = GetHwTonemapFilter(options, "videotoolbox", "nv12", isMjpegEncoder);
 05710                mainFilters.Add(tonemapFilter);
 5711            }
 5712
 5713            /* Make sub and overlay filters for subtitle stream */
 05714            var subFilters = new List<string>();
 05715            var overlayFilters = new List<string>();
 5716
 05717            if (hasSubs)
 5718            {
 05719                if (hasGraphicalSubs)
 5720                {
 05721                    var subW = state.SubtitleStream?.Width;
 05722                    var subH = state.SubtitleStream?.Height;
 05723                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05724                    subFilters.Add(subPreProcFilters);
 05725                    subFilters.Add("format=bgra");
 5726                }
 05727                else if (hasTextSubs)
 5728                {
 05729                    var framerate = state.VideoStream?.RealFrameRate;
 05730                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5731
 05732                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05733                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05734                    subFilters.Add(alphaSrcFilter);
 05735                    subFilters.Add("format=bgra");
 05736                    subFilters.Add(subTextSubtitlesFilter);
 5737                }
 5738
 05739                subFilters.Add("hwupload");
 05740                overlayFilters.Add("overlay_videotoolbox=eof_action=pass:repeatlast=0");
 5741            }
 5742
 05743            if (usingHwSurface)
 5744            {
 05745                if (!isVtEncoder)
 5746                {
 05747                    mainFilters.Add("hwdownload");
 05748                    mainFilters.Add("format=nv12");
 5749                }
 5750
 05751                return (mainFilters, subFilters, overlayFilters);
 5752            }
 5753
 5754            // For old jellyfin-ffmpeg that has broken hwsurface, add a hwupload
 05755            var needFiltering = mainFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05756                                subFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05757                                overlayFilters.Any(f => !string.IsNullOrEmpty(f));
 05758            if (needFiltering)
 5759            {
 5760                // INPUT videotoolbox/memory surface(vram/uma)
 5761                // this will pass-through automatically if in/out format matches.
 05762                mainFilters.Insert(0, "hwupload");
 05763                mainFilters.Insert(0, "format=nv12|p010le|videotoolbox_vld");
 5764
 05765                if (!isVtEncoder)
 5766                {
 05767                    mainFilters.Add("hwdownload");
 05768                    mainFilters.Add("format=nv12");
 5769                }
 5770            }
 5771
 05772            return (mainFilters, subFilters, overlayFilters);
 5773        }
 5774
 5775        /// <summary>
 5776        /// Gets the parameter of Rockchip RKMPP/RKRGA filter chain.
 5777        /// </summary>
 5778        /// <param name="state">Encoding state.</param>
 5779        /// <param name="options">Encoding options.</param>
 5780        /// <param name="vidEncoder">Video encoder to use.</param>
 5781        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5782        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFilterChain(
 5783            EncodingJobInfo state,
 5784            EncodingOptions options,
 5785            string vidEncoder)
 5786        {
 05787            if (options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 5788            {
 05789                return (null, null, null);
 5790            }
 5791
 05792            var isLinux = OperatingSystem.IsLinux();
 05793            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05794            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05795            var isSwEncoder = !vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05796            var isRkmppOclSupported = isLinux && IsRkmppFullSupported() && IsOpenclFullSupported();
 5797
 05798            if ((isSwDecoder && isSwEncoder)
 05799                || !isRkmppOclSupported
 05800                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5801            {
 05802                return GetSwVidFilterChain(state, options, vidEncoder);
 5803            }
 5804
 5805            // preferred rkmpp + rkrga + opencl filters pipeline
 05806            if (isRkmppOclSupported)
 5807            {
 05808                return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5809            }
 5810
 05811            return (null, null, null);
 5812        }
 5813
 5814        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFiltersPrefer
 5815            EncodingJobInfo state,
 5816            EncodingOptions options,
 5817            string vidDecoder,
 5818            string vidEncoder)
 5819        {
 05820            var inW = state.VideoStream?.Width;
 05821            var inH = state.VideoStream?.Height;
 05822            var reqW = state.BaseRequest.Width;
 05823            var reqH = state.BaseRequest.Height;
 05824            var reqMaxW = state.BaseRequest.MaxWidth;
 05825            var reqMaxH = state.BaseRequest.MaxHeight;
 05826            var threeDFormat = state.MediaSource.Video3DFormat;
 5827
 05828            var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05829            var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05830            var isSwDecoder = !isRkmppDecoder;
 05831            var isSwEncoder = !isRkmppEncoder;
 05832            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05833            var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder;
 05834            var isEncoderSupportAfbc = isRkmppEncoder
 05835                && (vidEncoder.Contains("h264", StringComparison.OrdinalIgnoreCase)
 05836                    || vidEncoder.Contains("hevc", StringComparison.OrdinalIgnoreCase));
 5837
 05838            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05839            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05840            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05841            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5842
 05843            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05844            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05845            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05846            var hasAssSubs = hasSubs
 05847                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05848                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 05849            var subW = state.SubtitleStream?.Width;
 05850            var subH = state.SubtitleStream?.Height;
 5851
 05852            var rotation = state.VideoStream?.Rotation ?? 0;
 05853            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05854            var doRkVppTranspose = !string.IsNullOrEmpty(transposeDir);
 05855            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isRkmppDecoder && doRkVppTranspose));
 05856            var swpInW = swapWAndH ? inH : inW;
 05857            var swpInH = swapWAndH ? inW : inH;
 5858
 5859            /* Make main filters for video stream */
 05860            var mainFilters = new List<string>();
 5861
 05862            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5863
 05864            if (isSwDecoder)
 5865            {
 5866                // INPUT sw surface(memory)
 5867                // sw deint
 05868                if (doDeintH2645)
 5869                {
 05870                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05871                    mainFilters.Add(swDeintFilter);
 5872                }
 5873
 05874                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 05875                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05876                if (isMjpegEncoder && !doOclTonemap)
 5877                {
 5878                    // sw decoder + hw mjpeg encoder
 05879                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5880                }
 5881
 05882                if (!string.IsNullOrEmpty(swScaleFilter))
 5883                {
 05884                    swScaleFilter += ":flags=fast_bilinear";
 5885                }
 5886
 5887                // sw scale
 05888                mainFilters.Add(swScaleFilter);
 05889                mainFilters.Add($"format={outFormat}");
 5890
 5891                // keep video at memory except ocl tonemap,
 5892                // since the overhead caused by hwupload >>> using sw filter.
 5893                // sw => hw
 05894                if (doOclTonemap)
 5895                {
 05896                    mainFilters.Add("hwupload=derive_device=opencl");
 5897                }
 5898            }
 05899            else if (isRkmppDecoder)
 5900            {
 5901                // INPUT rkmpp/drm surface(gem/dma-heap)
 5902
 05903                var isFullAfbcPipeline = isEncoderSupportAfbc && isDrmInDrmOut && !doOclTonemap;
 05904                var swapOutputWandH = doRkVppTranspose && swapWAndH;
 05905                var outFormat = doOclTonemap ? "p010" : (isMjpegEncoder ? "bgra" : "nv12"); // RGA only support full ran
 05906                var hwScaleFilter = GetHwScaleFilter("vpp", "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, r
 05907                var doScaling = GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, swpInW, swpInH, reqW, re
 5908
 05909                if (!hasSubs
 05910                     || doRkVppTranspose
 05911                     || !isFullAfbcPipeline
 05912                     || !string.IsNullOrEmpty(doScaling))
 5913                {
 5914                    // RGA3 hardware only support (1/8 ~ 8) scaling in each blit operation,
 5915                    // but in Trickplay there's a case: (3840/320 == 12), enable 2pass for it
 05916                    if (!string.IsNullOrEmpty(doScaling)
 05917                        && !IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f))
 5918                    {
 5919                        // Vendor provided BSP kernel has an RGA driver bug that causes the output to be corrupted for P
 5920                        // Use NV15 instead of P010 to avoid the issue.
 5921                        // SDR inputs are using BGRA formats already which is not affected.
 05922                        var intermediateFormat = string.Equals(outFormat, "p010", StringComparison.OrdinalIgnoreCase) ? 
 05923                        var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={intermediateFormat}:force_d
 05924                        mainFilters.Add(hwScaleFilterFirstPass);
 5925                    }
 5926
 05927                    if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose)
 5928                    {
 05929                        hwScaleFilter += $":transpose={transposeDir}";
 5930                    }
 5931
 5932                    // try enabling AFBC to save DDR bandwidth
 05933                    if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline)
 5934                    {
 05935                        hwScaleFilter += ":afbc=1";
 5936                    }
 5937
 5938                    // hw transpose & scale
 05939                    mainFilters.Add(hwScaleFilter);
 5940                }
 5941            }
 5942
 05943            if (doOclTonemap && isRkmppDecoder)
 5944            {
 5945                // map from rkmpp/drm to opencl via drm-opencl interop.
 05946                mainFilters.Add("hwmap=derive_device=opencl");
 5947            }
 5948
 5949            // ocl tonemap
 05950            if (doOclTonemap)
 5951            {
 05952                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05953                mainFilters.Add(tonemapFilter);
 5954            }
 5955
 05956            var memoryOutput = false;
 05957            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 05958            if ((isRkmppDecoder && isSwEncoder) || isUploadForOclTonemap)
 5959            {
 05960                memoryOutput = true;
 5961
 5962                // OUTPUT nv12 surface(memory)
 05963                mainFilters.Add("hwdownload");
 05964                mainFilters.Add("format=nv12");
 5965            }
 5966
 5967            // OUTPUT nv12 surface(memory)
 05968            if (isSwDecoder && isRkmppEncoder)
 5969            {
 05970                memoryOutput = true;
 5971            }
 5972
 05973            if (memoryOutput)
 5974            {
 5975                // text subtitles
 05976                if (hasTextSubs)
 5977                {
 05978                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05979                    mainFilters.Add(textSubtitlesFilter);
 5980                }
 5981            }
 5982
 05983            if (isDrmInDrmOut)
 5984            {
 05985                if (doOclTonemap)
 5986                {
 5987                    // OUTPUT drm(nv12) surface(gem/dma-heap)
 5988                    // reverse-mapping via drm-opencl interop.
 05989                    mainFilters.Add("hwmap=derive_device=rkmpp:reverse=1");
 05990                    mainFilters.Add("format=drm_prime");
 5991                }
 5992            }
 5993
 5994            /* Make sub and overlay filters for subtitle stream */
 05995            var subFilters = new List<string>();
 05996            var overlayFilters = new List<string>();
 05997            if (isDrmInDrmOut)
 5998            {
 05999                if (hasSubs)
 6000                {
 06001                    if (hasGraphicalSubs)
 6002                    {
 06003                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 06004                        subFilters.Add(subPreProcFilters);
 06005                        subFilters.Add("format=bgra");
 6006                    }
 06007                    else if (hasTextSubs)
 6008                    {
 06009                        var framerate = state.VideoStream?.RealFrameRate;
 06010                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 6011
 6012                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 06013                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 06014                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 06015                        subFilters.Add(alphaSrcFilter);
 06016                        subFilters.Add("format=bgra");
 06017                        subFilters.Add(subTextSubtitlesFilter);
 6018                    }
 6019
 06020                    subFilters.Add("hwupload=derive_device=rkmpp");
 6021
 6022                    // try enabling AFBC to save DDR bandwidth
 06023                    var hwOverlayFilter = "overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12";
 06024                    if (isEncoderSupportAfbc)
 6025                    {
 06026                        hwOverlayFilter += ":afbc=1";
 6027                    }
 6028
 06029                    overlayFilters.Add(hwOverlayFilter);
 6030                }
 6031            }
 06032            else if (memoryOutput)
 6033            {
 06034                if (hasGraphicalSubs)
 6035                {
 06036                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 06037                    subFilters.Add(subPreProcFilters);
 06038                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 6039                }
 6040            }
 6041
 06042            return (mainFilters, subFilters, overlayFilters);
 6043        }
 6044
 6045        /// <summary>
 6046        /// Gets the parameter of video processing filters.
 6047        /// </summary>
 6048        /// <param name="state">Encoding state.</param>
 6049        /// <param name="options">Encoding options.</param>
 6050        /// <param name="outputVideoCodec">Video codec to use.</param>
 6051        /// <returns>The video processing filters parameter.</returns>
 6052        public string GetVideoProcessingFilterParam(
 6053            EncodingJobInfo state,
 6054            EncodingOptions options,
 6055            string outputVideoCodec)
 6056        {
 06057            var videoStream = state.VideoStream;
 06058            if (videoStream is null)
 6059            {
 06060                return string.Empty;
 6061            }
 6062
 06063            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 06064            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 06065            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 6066
 6067            List<string> mainFilters;
 6068            List<string> subFilters;
 6069            List<string> overlayFilters;
 6070
 06071            (mainFilters, subFilters, overlayFilters) = options.HardwareAccelerationType switch
 06072            {
 06073                HardwareAccelerationType.vaapi => GetVaapiVidFilterChain(state, options, outputVideoCodec),
 06074                HardwareAccelerationType.amf => GetAmdVidFilterChain(state, options, outputVideoCodec),
 06075                HardwareAccelerationType.qsv => GetIntelVidFilterChain(state, options, outputVideoCodec),
 06076                HardwareAccelerationType.nvenc => GetNvidiaVidFilterChain(state, options, outputVideoCodec),
 06077                HardwareAccelerationType.videotoolbox => GetAppleVidFilterChain(state, options, outputVideoCodec),
 06078                HardwareAccelerationType.rkmpp => GetRkmppVidFilterChain(state, options, outputVideoCodec),
 06079                _ => GetSwVidFilterChain(state, options, outputVideoCodec),
 06080            };
 6081
 06082            mainFilters?.RemoveAll(string.IsNullOrEmpty);
 06083            subFilters?.RemoveAll(string.IsNullOrEmpty);
 06084            overlayFilters?.RemoveAll(string.IsNullOrEmpty);
 6085
 06086            var framerate = GetFramerateParam(state);
 06087            if (framerate.HasValue)
 6088            {
 06089                mainFilters.Insert(0, string.Format(
 06090                    CultureInfo.InvariantCulture,
 06091                    "fps={0}",
 06092                    framerate.Value));
 6093            }
 6094
 06095            var mainStr = string.Empty;
 06096            if (mainFilters?.Count > 0)
 6097            {
 06098                mainStr = string.Format(
 06099                    CultureInfo.InvariantCulture,
 06100                    "{0}",
 06101                    string.Join(',', mainFilters));
 6102            }
 6103
 06104            if (overlayFilters?.Count == 0)
 6105            {
 6106                // -vf "scale..."
 06107                return string.IsNullOrEmpty(mainStr) ? string.Empty : " -vf \"" + mainStr + "\"";
 6108            }
 6109
 06110            if (overlayFilters?.Count > 0
 06111                && subFilters?.Count > 0
 06112                && state.SubtitleStream is not null)
 6113            {
 6114                // overlay graphical/text subtitles
 06115                var subStr = string.Format(
 06116                        CultureInfo.InvariantCulture,
 06117                        "{0}",
 06118                        string.Join(',', subFilters));
 6119
 06120                var overlayStr = string.Format(
 06121                        CultureInfo.InvariantCulture,
 06122                        "{0}",
 06123                        string.Join(',', overlayFilters));
 6124
 06125                var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
 06126                var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 06127                var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 6128
 06129                if (hasSubs)
 6130                {
 6131                    // -filter_complex "[0:s]scale=s[sub]..."
 06132                    var filterStr = string.IsNullOrEmpty(mainStr)
 06133                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]{5}\""
 06134                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6135
 06136                    if (hasTextSubs)
 6137                    {
 06138                        filterStr = string.IsNullOrEmpty(mainStr)
 06139                            ? " -filter_complex \"{4}[sub];[0:{2}][sub]{5}\""
 06140                            : " -filter_complex \"{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6141                    }
 6142
 06143                    return string.Format(
 06144                        CultureInfo.InvariantCulture,
 06145                        filterStr,
 06146                        mapPrefix,
 06147                        subtitleStreamIndex,
 06148                        videoStreamIndex,
 06149                        mainStr,
 06150                        subStr,
 06151                        overlayStr);
 6152                }
 6153            }
 6154
 06155            return string.Empty;
 6156        }
 6157
 6158        public string GetOverwriteColorPropertiesParam(EncodingJobInfo state, bool isTonemapAvailable)
 6159        {
 06160            if (isTonemapAvailable)
 6161            {
 06162                return GetInputHdrParam(state.VideoStream?.ColorTransfer);
 6163            }
 6164
 06165            return GetOutputSdrParam(null);
 6166        }
 6167
 6168        public string GetInputHdrParam(string colorTransfer)
 6169        {
 06170            if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
 6171            {
 6172                // HLG
 06173                return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
 6174            }
 6175
 6176            // HDR10
 06177            return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc";
 6178        }
 6179
 6180        public string GetOutputSdrParam(string tonemappingRange)
 6181        {
 6182            // SDR
 06183            if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
 6184            {
 06185                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
 6186            }
 6187
 06188            if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
 6189            {
 06190                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc";
 6191            }
 6192
 06193            return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
 6194        }
 6195
 6196        public static int GetVideoColorBitDepth(EncodingJobInfo state)
 6197        {
 06198            var videoStream = state.VideoStream;
 06199            if (videoStream is not null)
 6200            {
 06201                if (videoStream.BitDepth.HasValue)
 6202                {
 06203                    return videoStream.BitDepth.Value;
 6204                }
 6205
 06206                if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
 06207                    || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
 06208                    || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 06209                    || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
 6210                {
 06211                    return 8;
 6212                }
 6213
 06214                if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 06215                    || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 06216                    || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
 6217                {
 06218                    return 10;
 6219                }
 6220
 06221                if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 06222                    || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 06223                    || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
 6224                {
 06225                    return 12;
 6226                }
 6227
 06228                return 8;
 6229            }
 6230
 06231            return 0;
 6232        }
 6233
 6234        /// <summary>
 6235        /// Gets the ffmpeg option string for the hardware accelerated video decoder.
 6236        /// </summary>
 6237        /// <param name="state">The encoding job info.</param>
 6238        /// <param name="options">The encoding options.</param>
 6239        /// <returns>The option string or null if none available.</returns>
 6240        protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
 6241        {
 06242            var videoStream = state.VideoStream;
 06243            var mediaSource = state.MediaSource;
 06244            if (videoStream is null || mediaSource is null)
 6245            {
 06246                return null;
 6247            }
 6248
 6249            // HWA decoders can handle both video files and video folders.
 06250            var videoType = state.VideoType;
 06251            if (videoType != VideoType.VideoFile
 06252                && videoType != VideoType.Iso
 06253                && videoType != VideoType.Dvd
 06254                && videoType != VideoType.BluRay)
 6255            {
 06256                return null;
 6257            }
 6258
 06259            if (IsCopyCodec(state.OutputVideoCodec))
 6260            {
 06261                return null;
 6262            }
 6263
 06264            var hardwareAccelerationType = options.HardwareAccelerationType;
 6265
 06266            if (!string.IsNullOrEmpty(videoStream.Codec) && hardwareAccelerationType != HardwareAccelerationType.none)
 6267            {
 06268                var bitDepth = GetVideoColorBitDepth(state);
 6269
 6270                // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support for most platforms
 06271                if (bitDepth == 10
 06272                    && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06273                         || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
 06274                         || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)
 06275                         || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)))
 6276                {
 6277                    // RKMPP has H.264 Hi10P decoder
 06278                    bool hasHardwareHi10P = hardwareAccelerationType == HardwareAccelerationType.rkmpp;
 6279
 6280                    // VideoToolbox on Apple Silicon has H.264 Hi10P mode enabled after macOS 14.6
 06281                    if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox)
 6282                    {
 06283                        var ver = Environment.OSVersion.Version;
 06284                        var arch = RuntimeInformation.OSArchitecture;
 06285                        if (arch.Equals(Architecture.Arm64) && ver >= new Version(14, 6))
 6286                        {
 06287                            hasHardwareHi10P = true;
 6288                        }
 6289                    }
 6290
 06291                    if (!hasHardwareHi10P
 06292                        && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6293                    {
 06294                        return null;
 6295                    }
 6296                }
 6297
 06298                var decoder = hardwareAccelerationType switch
 06299                {
 06300                    HardwareAccelerationType.vaapi => GetVaapiVidDecoder(state, options, videoStream, bitDepth),
 06301                    HardwareAccelerationType.amf => GetAmfVidDecoder(state, options, videoStream, bitDepth),
 06302                    HardwareAccelerationType.qsv => GetQsvHwVidDecoder(state, options, videoStream, bitDepth),
 06303                    HardwareAccelerationType.nvenc => GetNvdecVidDecoder(state, options, videoStream, bitDepth),
 06304                    HardwareAccelerationType.videotoolbox => GetVideotoolboxVidDecoder(state, options, videoStream, bitD
 06305                    HardwareAccelerationType.rkmpp => GetRkmppVidDecoder(state, options, videoStream, bitDepth),
 06306                    _ => string.Empty
 06307                };
 6308
 06309                if (!string.IsNullOrEmpty(decoder))
 6310                {
 06311                    return decoder;
 6312                }
 6313            }
 6314
 6315            // leave blank so ffmpeg will decide
 06316            return null;
 6317        }
 6318
 6319        /// <summary>
 6320        /// Gets a hw decoder name.
 6321        /// </summary>
 6322        /// <param name="options">Encoding options.</param>
 6323        /// <param name="decoderPrefix">Decoder prefix.</param>
 6324        /// <param name="decoderSuffix">Decoder suffix.</param>
 6325        /// <param name="videoCodec">Video codec to use.</param>
 6326        /// <param name="bitDepth">Video color bit depth.</param>
 6327        /// <returns>Hardware decoder name.</returns>
 6328        public string GetHwDecoderName(EncodingOptions options, string decoderPrefix, string decoderSuffix, string video
 6329        {
 06330            if (string.IsNullOrEmpty(decoderPrefix) || string.IsNullOrEmpty(decoderSuffix))
 6331            {
 06332                return null;
 6333            }
 6334
 06335            var decoderName = decoderPrefix + '_' + decoderSuffix;
 6336
 06337            var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains
 6338
 6339            // VideoToolbox decoders have built-in SW fallback
 06340            if (bitDepth == 10
 06341                && isCodecAvailable
 06342                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6343            {
 06344                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06345                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
 06346                    && !options.EnableDecodingColorDepth10Hevc)
 6347                {
 06348                    return null;
 6349                }
 6350
 06351                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06352                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06353                    && !options.EnableDecodingColorDepth10Vp9)
 6354                {
 06355                    return null;
 6356                }
 6357            }
 6358
 06359            if (string.Equals(decoderSuffix, "cuvid", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdec
 6360            {
 06361                return null;
 6362            }
 6363
 06364            if (string.Equals(decoderSuffix, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwD
 6365            {
 06366                return null;
 6367            }
 6368
 06369            if (string.Equals(decoderSuffix, "rkmpp", StringComparison.OrdinalIgnoreCase))
 6370            {
 06371                return null;
 6372            }
 6373
 06374            return isCodecAvailable ? (" -c:v " + decoderName) : null;
 6375        }
 6376
 6377        /// <summary>
 6378        /// Gets a hwaccel type to use as a hardware decoder depending on the system.
 6379        /// </summary>
 6380        /// <param name="state">Encoding state.</param>
 6381        /// <param name="options">Encoding options.</param>
 6382        /// <param name="videoCodec">Video codec to use.</param>
 6383        /// <param name="bitDepth">Video color bit depth.</param>
 6384        /// <param name="outputHwSurface">Specifies if output hw surface.</param>
 6385        /// <returns>Hardware accelerator type.</returns>
 6386        public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, int bitDepth, bo
 6387        {
 06388            var isWindows = OperatingSystem.IsWindows();
 06389            var isLinux = OperatingSystem.IsLinux();
 06390            var isMacOS = OperatingSystem.IsMacOS();
 06391            var isD3d11Supported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va");
 06392            var isVaapiSupported = isLinux && IsVaapiSupported(state);
 06393            var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported();
 06394            var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv");
 06395            var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
 06396            var isRkmppSupported = isLinux && IsRkmppFullSupported();
 06397            var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCas
 06398            var hardwareAccelerationType = options.HardwareAccelerationType;
 6399
 06400            var ffmpegVersion = _mediaEncoder.EncoderVersion;
 6401
 6402            // Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
 06403            var isAv1 = ffmpegVersion < _minFFmpegImplicitHwaccel
 06404                && string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
 6405
 6406            // Allow profile mismatch if decoding H.264 baseline with d3d11va and vaapi hwaccels.
 06407            var profileMismatch = string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
 06408                && string.Equals(state.VideoStream?.Profile, "baseline", StringComparison.OrdinalIgnoreCase);
 6409
 6410            // Disable the extra internal copy in nvdec. We already handle it in filter chain.
 06411            var nvdecNoInternalCopy = ffmpegVersion >= _minFFmpegHwaUnsafeOutput;
 6412
 6413            // Strip the display rotation side data from the transposed fmp4 output stream.
 06414            var stripRotationData = (state.VideoStream?.Rotation ?? 0) != 0
 06415                && ffmpegVersion >= _minFFmpegDisplayRotationOption;
 06416            var stripRotationDataArgs = stripRotationData ? " -display_rotation 0" : string.Empty;
 6417
 6418            // VideoToolbox decoders have built-in SW fallback
 06419            if (isCodecAvailable
 06420                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6421            {
 06422                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06423                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase))
 6424                {
 06425                    if (IsVideoStreamHevcRext(state))
 6426                    {
 06427                        if (bitDepth <= 10 && !options.EnableDecodingColorDepth10HevcRext)
 6428                        {
 06429                            return null;
 6430                        }
 6431
 06432                        if (bitDepth == 12 && !options.EnableDecodingColorDepth12HevcRext)
 6433                        {
 06434                            return null;
 6435                        }
 6436
 06437                        if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06438                            && !_mediaEncoder.IsVaapiDeviceInteliHD)
 6439                        {
 06440                            return null;
 6441                        }
 6442                    }
 06443                    else if (bitDepth == 10 && !options.EnableDecodingColorDepth10Hevc)
 6444                    {
 06445                        return null;
 6446                    }
 6447                }
 6448
 06449                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06450                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06451                    && bitDepth == 10
 06452                    && !options.EnableDecodingColorDepth10Vp9)
 6453                {
 06454                    return null;
 6455                }
 6456            }
 6457
 6458            // Intel qsv/d3d11va/vaapi
 06459            if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 6460            {
 06461                if (options.PreferSystemNativeHwDecoder)
 6462                {
 06463                    if (isVaapiSupported && isCodecAvailable)
 6464                    {
 06465                        return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + st
 06466                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " 
 6467                    }
 6468
 06469                    if (isD3d11Supported && isCodecAvailable)
 6470                    {
 06471                        return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + 
 06472                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 
 6473                    }
 6474                }
 6475                else
 6476                {
 06477                    if (isQsvSupported && isCodecAvailable)
 6478                    {
 06479                        return " -hwaccel qsv" + (outputHwSurface ? " -hwaccel_output_format qsv -noautorotate" + stripR
 6480                    }
 6481                }
 6482            }
 6483
 6484            // Nvidia cuda
 06485            if (hardwareAccelerationType == HardwareAccelerationType.nvenc)
 6486            {
 06487                if (isCudaSupported && isCodecAvailable)
 6488                {
 06489                    if (options.EnableEnhancedNvdecDecoder)
 6490                    {
 6491                        // set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support.
 06492                        return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stri
 06493                            + (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + 
 6494                    }
 6495
 6496                    // cuvid decoder doesn't have threading issue.
 06497                    return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stripRot
 6498                }
 6499            }
 6500
 6501            // Amd d3d11va
 06502            if (hardwareAccelerationType == HardwareAccelerationType.amf)
 6503            {
 06504                if (isD3d11Supported && isCodecAvailable)
 6505                {
 06506                    return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + stri
 06507                        + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v
 6508                }
 6509            }
 6510
 6511            // Vaapi
 06512            if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06513                && isVaapiSupported
 06514                && isCodecAvailable)
 6515            {
 06516                return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + stripRotat
 06517                    + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1
 6518            }
 6519
 6520            // Apple videotoolbox
 06521            if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06522                && isVideotoolboxSupported
 06523                && isCodecAvailable)
 6524            {
 06525                return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string
 6526            }
 6527
 6528            // Rockchip rkmpp
 06529            if (hardwareAccelerationType == HardwareAccelerationType.rkmpp
 06530                && isRkmppSupported
 06531                && isCodecAvailable)
 6532            {
 06533                return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime -noautorotate" + stripR
 6534            }
 6535
 06536            return null;
 6537        }
 6538
 6539        public string GetQsvHwVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6540        {
 06541            var isWindows = OperatingSystem.IsWindows();
 06542            var isLinux = OperatingSystem.IsLinux();
 6543
 06544            if ((!isWindows && !isLinux)
 06545                || options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 6546            {
 06547                return null;
 6548            }
 6549
 06550            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 06551            var isIntelDx11OclSupported = isWindows
 06552                && _mediaEncoder.SupportsHwaccel("d3d11va")
 06553                && isQsvOclSupported;
 06554            var isIntelVaapiOclSupported = isLinux
 06555                && IsVaapiSupported(state)
 06556                && isQsvOclSupported;
 06557            var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
 06558                && _mediaEncoder.SupportsFilter("alphasrc");
 6559
 06560            var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06561                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06562            var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 06563            var is8_10_12bitSwFormatsQsv = is8_10bitSwFormatsQsv
 06564                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06565                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06566                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06567                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06568                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06569                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06570                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6571            // TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
 6572
 06573            if (is8bitSwFormatsQsv)
 6574            {
 06575                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06576                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6577                {
 06578                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6579                }
 6580
 06581                if (string.Equals(videoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase))
 6582                {
 06583                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6584                }
 6585
 06586                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6587                {
 06588                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6589                }
 6590
 06591                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6592                {
 06593                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6594                }
 6595            }
 6596
 06597            if (is8_10bitSwFormatsQsv)
 6598            {
 06599                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6600                {
 06601                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6602                }
 6603
 06604                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 6605                {
 06606                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6607                }
 6608            }
 6609
 06610            if (is8_10_12bitSwFormatsQsv)
 6611            {
 06612                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06613                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6614                {
 06615                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6616                }
 6617            }
 6618
 06619            return null;
 6620        }
 6621
 6622        public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6623        {
 06624            if ((!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
 06625                || options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 6626            {
 06627                return null;
 6628            }
 6629
 06630            var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
 06631            var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06632                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06633            var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06634            var is8_10_12bitSwFormatsNvdec = is8_10bitSwFormatsNvdec
 06635                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06636                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06637                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06638                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6639            // TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
 6640
 06641            if (is8bitSwFormatsNvdec)
 6642            {
 06643                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06644                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6645                {
 06646                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6647                }
 6648
 06649                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6650                {
 06651                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6652                }
 6653
 06654                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6655                {
 06656                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6657                }
 6658
 06659                if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6660                {
 06661                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpe
 6662                }
 6663
 06664                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6665                {
 06666                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6667                }
 6668            }
 6669
 06670            if (is8_10bitSwFormatsNvdec)
 6671            {
 06672                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6673                {
 06674                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6675                }
 6676
 06677                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6678                {
 06679                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6680                }
 6681            }
 6682
 06683            if (is8_10_12bitSwFormatsNvdec)
 6684            {
 06685                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06686                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6687                {
 06688                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6689                }
 6690            }
 6691
 06692            return null;
 6693        }
 6694
 6695        public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitD
 6696        {
 06697            if (!OperatingSystem.IsWindows()
 06698                || options.HardwareAccelerationType != HardwareAccelerationType.amf)
 6699            {
 06700                return null;
 6701            }
 6702
 06703            var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
 06704                && IsOpenclFullSupported()
 06705                && _mediaEncoder.SupportsFilter("alphasrc");
 06706            var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06707                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06708            var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 6709
 06710            if (is8bitSwFormatsAmf)
 6711            {
 06712                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06713                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6714                {
 06715                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6716                }
 6717
 06718                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6719                {
 06720                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6721                }
 6722
 06723                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6724                {
 06725                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6726                }
 6727            }
 6728
 06729            if (is8_10bitSwFormatsAmf)
 6730            {
 06731                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06732                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6733                {
 06734                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6735                }
 6736
 06737                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6738                {
 06739                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6740                }
 6741
 06742                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6743                {
 06744                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6745                }
 6746            }
 6747
 06748            return null;
 6749        }
 6750
 6751        public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6752        {
 06753            if (!OperatingSystem.IsLinux()
 06754                || options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 6755            {
 06756                return null;
 6757            }
 6758
 06759            var hwSurface = IsVaapiSupported(state)
 06760                && IsVaapiFullSupported()
 06761                && IsOpenclFullSupported()
 06762                && _mediaEncoder.SupportsFilter("alphasrc");
 06763            var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06764                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06765            var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06766            var is8_10_12bitSwFormatsVaapi = is8_10bitSwFormatsVaapi
 06767                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06768                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06769                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06770                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06771                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06772                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06773                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6774
 06775            if (is8bitSwFormatsVaapi)
 6776            {
 06777                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06778                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6779                {
 06780                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6781                }
 6782
 06783                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6784                {
 06785                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6786                }
 6787
 06788                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6789                {
 06790                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6791                }
 6792
 06793                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6794                {
 06795                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6796                }
 6797            }
 6798
 06799            if (is8_10bitSwFormatsVaapi)
 6800            {
 06801                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6802                {
 06803                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6804                }
 6805
 06806                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6807                {
 06808                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6809                }
 6810            }
 6811
 06812            if (is8_10_12bitSwFormatsVaapi)
 6813            {
 06814                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06815                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6816                {
 06817                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6818                }
 6819            }
 6820
 06821            return null;
 6822        }
 6823
 6824        public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream,
 6825        {
 06826            if (!OperatingSystem.IsMacOS()
 06827                || options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 6828            {
 06829                return null;
 6830            }
 6831
 06832            var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase
 06833                                    || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnore
 06834            var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, String
 06835            var is8_10_12bitSwFormatsVt = is8_10bitSwFormatsVt
 06836                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06837                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06838                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06839                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06840                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06841                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06842                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 06843            var isAv1SupportedSwFormatsVt = is8_10bitSwFormatsVt || string.Equals("yuv420p12le", videoStream.PixelFormat
 6844
 6845            // The related patches make videotoolbox hardware surface working is only available in jellyfin-ffmpeg 7.0.1
 06846            bool useHwSurface = (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface) && IsVideoToolboxFullSupp
 6847
 06848            if (is8bitSwFormatsVt)
 6849            {
 06850                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6851                {
 06852                    return GetHwaccelType(state, options, "vp8", bitDepth, useHwSurface);
 6853                }
 6854            }
 6855
 06856            if (is8_10bitSwFormatsVt)
 6857            {
 06858                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06859                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6860                {
 06861                    return GetHwaccelType(state, options, "h264", bitDepth, useHwSurface);
 6862                }
 6863
 06864                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6865                {
 06866                    return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
 6867                }
 6868            }
 6869
 06870            if (is8_10_12bitSwFormatsVt)
 6871            {
 06872                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06873                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6874                {
 06875                    return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
 6876                }
 6877
 06878                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06879                    && isAv1SupportedSwFormatsVt
 06880                    && _mediaEncoder.IsVideoToolboxAv1DecodeAvailable)
 6881                {
 06882                    return GetHwaccelType(state, options, "av1", bitDepth, useHwSurface);
 6883                }
 6884            }
 6885
 06886            return null;
 6887        }
 6888
 6889        public string GetRkmppVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6890        {
 06891            var isLinux = OperatingSystem.IsLinux();
 6892
 06893            if (!isLinux
 06894                || options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 6895            {
 06896                return null;
 6897            }
 6898
 06899            var inW = state.VideoStream?.Width;
 06900            var inH = state.VideoStream?.Height;
 06901            var reqW = state.BaseRequest.Width;
 06902            var reqH = state.BaseRequest.Height;
 06903            var reqMaxW = state.BaseRequest.MaxWidth;
 06904            var reqMaxH = state.BaseRequest.MaxHeight;
 6905
 6906            // rkrga RGA2e supports range from 1/16 to 16
 06907            if (!IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 16.0f))
 6908            {
 06909                return null;
 6910            }
 6911
 06912            var isRkmppOclSupported = IsRkmppFullSupported() && IsOpenclFullSupported();
 06913            var hwSurface = isRkmppOclSupported
 06914                && _mediaEncoder.SupportsFilter("alphasrc");
 6915
 6916            // rkrga RGA3 supports range from 1/8 to 8
 06917            var isAfbcSupported = hwSurface && IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 6918
 6919            // TODO: add more 8/10bit and 4:2:2 formats for Rkmpp after finishing the ffcheck tool
 06920            var is8bitSwFormatsRkmpp = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06921                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06922            var is10bitSwFormatsRkmpp = string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIg
 06923            var is8_10bitSwFormatsRkmpp = is8bitSwFormatsRkmpp || is10bitSwFormatsRkmpp;
 6924
 6925            // nv15 and nv20 are bit-stream only formats
 06926            if (is10bitSwFormatsRkmpp && !hwSurface)
 6927            {
 06928                return null;
 6929            }
 6930
 06931            if (is8bitSwFormatsRkmpp)
 6932            {
 06933                if (string.Equals(videoStream.Codec, "mpeg1video", StringComparison.OrdinalIgnoreCase))
 6934                {
 06935                    return GetHwaccelType(state, options, "mpeg1video", bitDepth, hwSurface);
 6936                }
 6937
 06938                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6939                {
 06940                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6941                }
 6942
 06943                if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 6944                {
 06945                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
 6946                }
 6947
 06948                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6949                {
 06950                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6951                }
 6952            }
 6953
 06954            if (is8_10bitSwFormatsRkmpp)
 6955            {
 06956                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06957                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6958                {
 06959                    var accelType = GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 06960                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 6961                }
 6962
 06963                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06964                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6965                {
 06966                    var accelType = GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 06967                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 6968                }
 6969
 06970                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6971                {
 06972                    var accelType = GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 06973                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 6974                }
 6975
 06976                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 6977                {
 06978                    var accelType = GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 06979                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 6980                }
 6981            }
 6982
 06983            return null;
 6984        }
 6985
 6986        /// <summary>
 6987        /// Gets the number of threads.
 6988        /// </summary>
 6989        /// <param name="state">Encoding state.</param>
 6990        /// <param name="encodingOptions">Encoding options.</param>
 6991        /// <param name="outputVideoCodec">Video codec to use.</param>
 6992        /// <returns>Number of threads.</returns>
 6993#nullable enable
 6994        public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVide
 6995        {
 06996            var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount;
 6997
 06998            if (threads <= 0)
 6999            {
 7000                // Automatically set thread count
 07001                return 0;
 7002            }
 7003
 07004            return Math.Min(threads, Environment.ProcessorCount);
 7005        }
 7006
 7007#nullable disable
 7008        public void TryStreamCopy(EncodingJobInfo state)
 7009        {
 07010            if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream))
 7011            {
 07012                state.OutputVideoCodec = "copy";
 7013            }
 7014            else
 7015            {
 07016                var user = state.User;
 7017
 7018                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07019                if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
 7020                {
 07021                    state.OutputVideoCodec = "copy";
 7022                }
 7023            }
 7024
 07025            if (state.AudioStream is not null
 07026                && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs))
 7027            {
 07028                state.OutputAudioCodec = "copy";
 7029            }
 7030            else
 7031            {
 07032                var user = state.User;
 7033
 7034                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07035                if (user is not null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
 7036                {
 07037                    state.OutputAudioCodec = "copy";
 7038                }
 7039            }
 07040        }
 7041
 7042        public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
 7043        {
 07044            var inputModifier = string.Empty;
 07045            var analyzeDurationArgument = string.Empty;
 7046
 7047            // Apply -analyzeduration as per the environment variable,
 7048            // otherwise ffmpeg will break on certain files due to default value is 0.
 07049            var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
 7050
 07051            if (state.MediaSource.AnalyzeDurationMs > 0)
 7052            {
 07053                analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToStr
 7054            }
 07055            else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
 7056            {
 07057                analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
 7058            }
 7059
 07060            if (!string.IsNullOrEmpty(analyzeDurationArgument))
 7061            {
 07062                inputModifier += " " + analyzeDurationArgument;
 7063            }
 7064
 07065            inputModifier = inputModifier.Trim();
 7066
 7067            // Apply -probesize if configured
 07068            var ffmpegProbeSize = _config.GetFFmpegProbeSize();
 7069
 07070            if (!string.IsNullOrEmpty(ffmpegProbeSize))
 7071            {
 07072                inputModifier += $" -probesize {ffmpegProbeSize}";
 7073            }
 7074
 07075            var userAgentParam = GetUserAgentParam(state);
 7076
 07077            if (!string.IsNullOrEmpty(userAgentParam))
 7078            {
 07079                inputModifier += " " + userAgentParam;
 7080            }
 7081
 07082            inputModifier = inputModifier.Trim();
 7083
 07084            var refererParam = GetRefererParam(state);
 7085
 07086            if (!string.IsNullOrEmpty(refererParam))
 7087            {
 07088                inputModifier += " " + refererParam;
 7089            }
 7090
 07091            inputModifier = inputModifier.Trim();
 7092
 07093            inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions, segmentContainer);
 07094            inputModifier = inputModifier.Trim();
 7095
 07096            if (state.InputProtocol == MediaProtocol.Rtsp)
 7097            {
 07098                inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
 7099            }
 7100
 07101            if (!string.IsNullOrEmpty(state.InputAudioSync))
 7102            {
 07103                inputModifier += " -async " + state.InputAudioSync;
 7104            }
 7105
 07106            if (!string.IsNullOrEmpty(state.InputVideoSync))
 7107            {
 07108                inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion);
 7109            }
 7110
 07111            if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp)
 7112            {
 07113                inputModifier += " -re";
 7114            }
 07115            else if (encodingOptions.EnableSegmentDeletion
 07116                && state.VideoStream is not null
 07117                && state.TranscodingType == TranscodingJobType.Hls
 07118                && IsCopyCodec(state.OutputVideoCodec)
 07119                && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateOption)
 7120            {
 7121                // Set an input read rate limit 10x for using SegmentDeletion with stream-copy
 7122                // to prevent ffmpeg from exiting prematurely (due to fast drive)
 07123                inputModifier += " -readrate 10";
 7124            }
 7125
 07126            var flags = new List<string>();
 07127            if (state.IgnoreInputDts)
 7128            {
 07129                flags.Add("+igndts");
 7130            }
 7131
 07132            if (state.IgnoreInputIndex)
 7133            {
 07134                flags.Add("+ignidx");
 7135            }
 7136
 07137            if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
 7138            {
 07139                flags.Add("+genpts");
 7140            }
 7141
 07142            if (state.DiscardCorruptFramesInput)
 7143            {
 07144                flags.Add("+discardcorrupt");
 7145            }
 7146
 07147            if (state.EnableFastSeekInput)
 7148            {
 07149                flags.Add("+fastseek");
 7150            }
 7151
 07152            if (flags.Count > 0)
 7153            {
 07154                inputModifier += " -fflags " + string.Join(string.Empty, flags);
 7155            }
 7156
 07157            if (state.IsVideoRequest)
 7158            {
 07159                if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && encodingOpt
 7160                {
 07161                    var inputFormat = GetInputFormat(state.InputContainer);
 07162                    if (!string.IsNullOrEmpty(inputFormat))
 7163                    {
 07164                        inputModifier += " -f " + inputFormat;
 7165                    }
 7166                }
 7167            }
 7168
 07169            if (state.MediaSource.RequiresLooping)
 7170            {
 07171                inputModifier += " -stream_loop -1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2";
 7172            }
 7173
 07174            return inputModifier;
 7175        }
 7176
 7177        public void AttachMediaSourceInfo(
 7178            EncodingJobInfo state,
 7179            EncodingOptions encodingOptions,
 7180            MediaSourceInfo mediaSource,
 7181            string requestedUrl)
 7182        {
 07183            ArgumentNullException.ThrowIfNull(state);
 7184
 07185            ArgumentNullException.ThrowIfNull(mediaSource);
 7186
 07187            var path = mediaSource.Path;
 07188            var protocol = mediaSource.Protocol;
 7189
 07190            if (!string.IsNullOrEmpty(mediaSource.EncoderPath) && mediaSource.EncoderProtocol.HasValue)
 7191            {
 07192                path = mediaSource.EncoderPath;
 07193                protocol = mediaSource.EncoderProtocol.Value;
 7194            }
 7195
 07196            state.MediaPath = path;
 07197            state.InputProtocol = protocol;
 07198            state.InputContainer = mediaSource.Container;
 07199            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07200            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 7201
 07202            state.IsoType = mediaSource.IsoType;
 7203
 07204            if (mediaSource.Timestamp.HasValue)
 7205            {
 07206                state.InputTimestamp = mediaSource.Timestamp.Value;
 7207            }
 7208
 07209            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07210            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 07211            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
 7212
 07213            if ((state.ReadInputAtNativeFramerate && !state.IsSegmentedLiveStream)
 07214                || (mediaSource.Protocol == MediaProtocol.File
 07215                && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)))
 7216            {
 07217                state.InputVideoSync = "-1";
 07218                state.InputAudioSync = "1";
 7219            }
 7220
 07221            if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase)
 07222                || string.Equals(mediaSource.Container, "asf", StringComparison.OrdinalIgnoreCase))
 7223            {
 7224                // Seeing some stuttering when transcoding wma to audio-only HLS
 07225                state.InputAudioSync = "1";
 7226            }
 7227
 07228            var mediaStreams = mediaSource.MediaStreams;
 7229
 07230            if (state.IsVideoRequest)
 7231            {
 07232                var videoRequest = state.BaseRequest;
 7233
 07234                if (string.IsNullOrEmpty(videoRequest.VideoCodec))
 7235                {
 07236                    if (string.IsNullOrEmpty(requestedUrl))
 7237                    {
 07238                        requestedUrl = "test." + videoRequest.Container;
 7239                    }
 7240
 07241                    videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
 7242                }
 7243
 07244                state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
 07245                state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Su
 07246                state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
 07247                state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
 7248
 07249                if (state.SubtitleStream is not null && !state.SubtitleStream.IsExternal)
 7250                {
 07251                    state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !
 7252                }
 7253
 07254                EnforceResolutionLimit(state);
 7255
 07256                NormalizeSubtitleEmbed(state);
 7257            }
 7258            else
 7259            {
 07260                state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
 7261            }
 7262
 07263            state.MediaSource = mediaSource;
 7264
 07265            var request = state.BaseRequest;
 07266            var supportedAudioCodecs = state.SupportedAudioCodecs;
 07267            if (request is not null && supportedAudioCodecs is not null && supportedAudioCodecs.Length > 0)
 7268            {
 07269                var supportedAudioCodecsList = supportedAudioCodecs.ToList();
 7270
 07271                ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
 7272
 07273                state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray();
 7274
 07275                request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(_mediaEncoder.CanEncodeToAudioCodec)
 07276                    ?? state.SupportedAudioCodecs.FirstOrDefault();
 7277            }
 7278
 07279            var supportedVideoCodecs = state.SupportedVideoCodecs;
 07280            if (request is not null && supportedVideoCodecs is not null && supportedVideoCodecs.Length > 0)
 7281            {
 07282                var supportedVideoCodecsList = supportedVideoCodecs.ToList();
 7283
 07284                ShiftVideoCodecsIfNeeded(supportedVideoCodecsList, encodingOptions);
 7285
 07286                state.SupportedVideoCodecs = supportedVideoCodecsList.ToArray();
 7287
 07288                request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
 7289            }
 07290        }
 7291
 7292        private void ShiftAudioCodecsIfNeeded(List<string> audioCodecs, MediaStream audioStream)
 7293        {
 7294            // No need to shift if there is only one supported audio codec.
 07295            if (audioCodecs.Count < 2)
 7296            {
 07297                return;
 7298            }
 7299
 07300            var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
 07301            var shiftAudioCodecs = new List<string>();
 07302            if (inputChannels >= 6)
 7303            {
 7304                // DTS and TrueHD are not supported by HLS
 7305                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07306                shiftAudioCodecs.Add("dts");
 07307                shiftAudioCodecs.Add("truehd");
 7308            }
 7309            else
 7310            {
 7311                // Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
 7312                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07313                shiftAudioCodecs.Add("ac3");
 07314                shiftAudioCodecs.Add("eac3");
 7315            }
 7316
 07317            if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7318            {
 07319                return;
 7320            }
 7321
 07322            while (shiftAudioCodecs.Contains(audioCodecs[0], StringComparison.OrdinalIgnoreCase))
 7323            {
 07324                var removed = audioCodecs[0];
 07325                audioCodecs.RemoveAt(0);
 07326                audioCodecs.Add(removed);
 7327            }
 07328        }
 7329
 7330        private void ShiftVideoCodecsIfNeeded(List<string> videoCodecs, EncodingOptions encodingOptions)
 7331        {
 7332            // No need to shift if there is only one supported video codec.
 07333            if (videoCodecs.Count < 2)
 7334            {
 07335                return;
 7336            }
 7337
 7338            // Shift codecs to the end of list if it's not allowed.
 07339            var shiftVideoCodecs = new List<string>();
 07340            if (!encodingOptions.AllowHevcEncoding)
 7341            {
 07342                shiftVideoCodecs.Add("hevc");
 07343                shiftVideoCodecs.Add("h265");
 7344            }
 7345
 07346            if (!encodingOptions.AllowAv1Encoding)
 7347            {
 07348                shiftVideoCodecs.Add("av1");
 7349            }
 7350
 07351            if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7352            {
 07353                return;
 7354            }
 7355
 07356            while (shiftVideoCodecs.Contains(videoCodecs[0], StringComparison.OrdinalIgnoreCase))
 7357            {
 07358                var removed = videoCodecs[0];
 07359                videoCodecs.RemoveAt(0);
 07360                videoCodecs.Add(removed);
 7361            }
 07362        }
 7363
 7364        private void NormalizeSubtitleEmbed(EncodingJobInfo state)
 7365        {
 07366            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7367            {
 07368                return;
 7369            }
 7370
 7371            // This is tricky to remux in, after converting to dvdsub it's not positioned correctly
 7372            // Therefore, let's just burn it in
 07373            if (string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 7374            {
 07375                state.SubtitleDeliveryMethod = SubtitleDeliveryMethod.Encode;
 7376            }
 07377        }
 7378
 7379        public string GetSubtitleEmbedArguments(EncodingJobInfo state)
 7380        {
 07381            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7382            {
 07383                return string.Empty;
 7384            }
 7385
 07386            var format = state.SupportedSubtitleCodecs.FirstOrDefault();
 7387            string codec;
 7388
 07389            if (string.IsNullOrEmpty(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.Ordin
 7390            {
 07391                codec = "copy";
 7392            }
 7393            else
 7394            {
 07395                codec = format;
 7396            }
 7397
 07398            return " -codec:s:0 " + codec + " -disposition:s:0 default";
 7399        }
 7400
 7401        public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, Encoder
 7402        {
 7403            // Get the output codec name
 07404            var videoCodec = GetVideoEncoder(state, encodingOptions);
 7405
 07406            var format = string.Empty;
 07407            var keyFrame = string.Empty;
 07408            var outputPath = state.OutputFilePath;
 7409
 07410            if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase)
 07411                && state.BaseRequest.Context == EncodingContext.Streaming)
 7412            {
 7413                // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
 07414                format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov";
 7415            }
 7416
 07417            var threads = GetNumberOfThreads(state, encodingOptions, videoCodec);
 7418
 07419            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7420
 07421            return string.Format(
 07422                CultureInfo.InvariantCulture,
 07423                "{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
 07424                inputModifier,
 07425                GetInputArgument(state, encodingOptions, null),
 07426                keyFrame,
 07427                GetMapArgs(state),
 07428                GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset),
 07429                threads,
 07430                GetProgressiveVideoAudioArguments(state, encodingOptions),
 07431                GetSubtitleEmbedArguments(state),
 07432                format,
 07433                outputPath).Trim();
 7434        }
 7435
 7436        public string GetOutputFFlags(EncodingJobInfo state)
 7437        {
 07438            var flags = new List<string>();
 07439            if (state.GenPtsOutput)
 7440            {
 07441                flags.Add("+genpts");
 7442            }
 7443
 07444            if (flags.Count > 0)
 7445            {
 07446                return " -fflags " + string.Join(string.Empty, flags);
 7447            }
 7448
 07449            return string.Empty;
 7450        }
 7451
 7452        public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoC
 7453        {
 07454            var args = "-codec:v:0 " + videoCodec;
 7455
 07456            if (state.BaseRequest.EnableMpegtsM2TsMode)
 7457            {
 07458                args += " -mpegts_m2ts_mode 1";
 7459            }
 7460
 07461            if (IsCopyCodec(videoCodec))
 7462            {
 07463                if (state.VideoStream is not null
 07464                    && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
 07465                    && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
 7466                {
 07467                    string bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Video);
 07468                    if (!string.IsNullOrEmpty(bitStreamArgs))
 7469                    {
 07470                        args += " " + bitStreamArgs;
 7471                    }
 7472                }
 7473
 07474                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7475                {
 07476                    args += " -copyts -avoid_negative_ts disabled -start_at_zero";
 7477                }
 7478
 07479                if (!state.RunTimeTicks.HasValue)
 7480                {
 07481                    args += " -fflags +genpts";
 7482                }
 7483            }
 7484            else
 7485            {
 07486                var keyFrameArg = string.Format(
 07487                    CultureInfo.InvariantCulture,
 07488                    " -force_key_frames \"expr:gte(t,n_forced*{0})\"",
 07489                    5);
 7490
 07491                args += keyFrameArg;
 7492
 07493                var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream &&
 7494
 07495                var hasCopyTs = false;
 7496
 7497                // video processing filters.
 07498                var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
 7499
 07500                var negativeMapArgs = GetNegativeMapArgsByFilters(state, videoProcessParam);
 7501
 07502                args = negativeMapArgs + args + videoProcessParam;
 7503
 07504                hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
 7505
 07506                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7507                {
 07508                    if (!hasCopyTs)
 7509                    {
 07510                        args += " -copyts";
 7511                    }
 7512
 07513                    args += " -avoid_negative_ts disabled";
 7514
 07515                    if (!(state.SubtitleStream is not null && state.SubtitleStream.IsExternal && !state.SubtitleStream.I
 7516                    {
 07517                        args += " -start_at_zero";
 7518                    }
 7519                }
 7520
 07521                var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset);
 7522
 07523                if (!string.IsNullOrEmpty(qualityParam))
 7524                {
 07525                    args += " " + qualityParam.Trim();
 7526                }
 7527            }
 7528
 07529            if (!string.IsNullOrEmpty(state.OutputVideoSync))
 7530            {
 07531                args += GetVideoSyncOption(state.OutputVideoSync, _mediaEncoder.EncoderVersion);
 7532            }
 7533
 07534            args += GetOutputFFlags(state);
 7535
 07536            return args;
 7537        }
 7538
 7539        public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions)
 7540        {
 7541            // If the video doesn't have an audio stream, return a default.
 07542            if (state.AudioStream is null && state.VideoStream is not null)
 7543            {
 07544                return string.Empty;
 7545            }
 7546
 7547            // Get the output codec name
 07548            var codec = GetAudioEncoder(state);
 7549
 07550            var args = "-codec:a:0 " + codec;
 7551
 07552            if (IsCopyCodec(codec))
 7553            {
 07554                return args;
 7555            }
 7556
 07557            var channels = state.OutputAudioChannels;
 7558
 07559            var useDownMixAlgorithm = state.AudioStream is not null
 07560                                      && DownMixAlgorithmsHelper.AlgorithmFilterStrings.ContainsKey((encodingOptions.Dow
 7561
 07562            if (channels.HasValue && !useDownMixAlgorithm)
 7563            {
 07564                args += " -ac " + channels.Value;
 7565            }
 7566
 07567            var bitrate = state.OutputAudioBitrate;
 07568            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
 7569            {
 07570                var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2);
 07571                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7572                {
 07573                    args += vbrParam;
 7574                }
 7575                else
 7576                {
 07577                    args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
 7578                }
 7579            }
 7580
 07581            if (state.OutputAudioSampleRate.HasValue)
 7582            {
 07583                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
 7584            }
 7585
 07586            args += GetAudioFilterParam(state, encodingOptions);
 7587
 07588            return args;
 7589        }
 7590
 7591        public string GetProgressiveAudioFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string 
 7592        {
 07593            var audioTranscodeParams = new List<string>();
 7594
 07595            var bitrate = state.OutputAudioBitrate;
 07596            var channels = state.OutputAudioChannels;
 07597            var outputCodec = state.OutputAudioCodec;
 7598
 07599            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
 7600            {
 07601                var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2);
 07602                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7603                {
 07604                    audioTranscodeParams.Add(vbrParam);
 7605                }
 7606                else
 7607                {
 07608                    audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
 7609                }
 7610            }
 7611
 07612            if (channels.HasValue)
 7613            {
 07614                audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)
 7615            }
 7616
 07617            if (!string.IsNullOrEmpty(outputCodec))
 7618            {
 07619                audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
 7620            }
 7621
 07622            if (GetAudioEncoder(state).StartsWith("pcm_", StringComparison.Ordinal))
 7623            {
 07624                audioTranscodeParams.Add(string.Concat("-f ", GetAudioEncoder(state).AsSpan(4)));
 07625                audioTranscodeParams.Add("-ar " + state.BaseRequest.AudioBitRate);
 7626            }
 7627
 07628            if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
 7629            {
 7630                // opus only supports specific sampling rates
 07631                var sampleRate = state.OutputAudioSampleRate;
 07632                if (sampleRate.HasValue)
 7633                {
 07634                    var sampleRateValue = sampleRate.Value switch
 07635                    {
 07636                        <= 8000 => 8000,
 07637                        <= 12000 => 12000,
 07638                        <= 16000 => 16000,
 07639                        <= 24000 => 24000,
 07640                        _ => 48000
 07641                    };
 7642
 07643                    audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
 7644                }
 7645            }
 7646
 7647            // Copy the movflags from GetProgressiveVideoFullCommandLine
 7648            // See #9248 and the associated PR for why this is needed
 07649            if (_mp4ContainerNames.Contains(state.OutputContainer))
 7650            {
 07651                audioTranscodeParams.Add("-movflags empty_moov+delay_moov");
 7652            }
 7653
 07654            var threads = GetNumberOfThreads(state, encodingOptions, null);
 7655
 07656            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7657
 07658            return string.Format(
 07659                CultureInfo.InvariantCulture,
 07660                "{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"",
 07661                inputModifier,
 07662                GetInputArgument(state, encodingOptions, null),
 07663                threads,
 07664                " -vn",
 07665                string.Join(' ', audioTranscodeParams),
 07666                outputPath,
 07667                string.Empty,
 07668                string.Empty,
 07669                string.Empty).Trim();
 7670        }
 7671
 7672        public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
 7673        {
 07674            var index = 0;
 07675            var length = mediaStreams.Count;
 7676
 07677            for (var i = 0; i < length; i++)
 7678            {
 07679                var currentMediaStream = mediaStreams[i];
 07680                if (currentMediaStream == streamToFind)
 7681                {
 07682                    return index;
 7683                }
 7684
 07685                if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
 7686                {
 07687                    index++;
 7688                }
 7689            }
 7690
 07691            return -1;
 7692        }
 7693
 7694        public static bool IsCopyCodec(string codec)
 7695        {
 07696            return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
 7697        }
 7698
 7699        private static bool ShouldEncodeSubtitle(EncodingJobInfo state)
 7700        {
 07701            return state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
 07702                   || (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec));
 7703        }
 7704
 7705        public static string GetVideoSyncOption(string videoSync, Version encoderVersion)
 7706        {
 07707            if (string.IsNullOrEmpty(videoSync))
 7708            {
 07709                return string.Empty;
 7710            }
 7711
 07712            if (encoderVersion >= new Version(5, 1))
 7713            {
 07714                if (int.TryParse(videoSync, CultureInfo.InvariantCulture, out var vsync))
 7715                {
 07716                    return vsync switch
 07717                    {
 07718                        -1 => " -fps_mode auto",
 07719                        0 => " -fps_mode passthrough",
 07720                        1 => " -fps_mode cfr",
 07721                        2 => " -fps_mode vfr",
 07722                        _ => string.Empty
 07723                    };
 7724                }
 7725
 07726                return string.Empty;
 7727            }
 7728
 7729            // -vsync is deprecated in FFmpeg 5.1 and will be removed in the future.
 07730            return $" -vsync {videoSync}";
 7731        }
 7732    }
 7733}

Methods/Properties

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