< 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: 3690
Coverable lines: 3716
Total lines: 7761
Line coverage: 0.6%
Branch coverage
0%
Covered branches: 0
Total branches: 3691
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%1332360%
GetHwTonemapFilter(...)0%1332360%
GetLibplaceboFilter(...)0%506220%
GetVideoTransposeDirection(...)0%156120%
GetSwVidFilterChain(...)0%2550500%
GetNvidiaVidFilterChain(...)0%110100%
GetNvidiaVidFiltersPrefered(...)0%7832880%
GetAmdVidFilterChain(...)0%210140%
GetAmdDx11VidFiltersPrefered(...)0%7832880%
GetIntelVidFilterChain(...)0%506220%
GetIntelQsvDx11VidFiltersPrefered(...)0%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%145201200%
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("opencl", StringComparison.OrdinalIgnoreCase))
 3495            {
 03496                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif;
 3497
 03498                if (_mediaEncoder.SupportsFilter("yadif_opencl")
 03499                    && _mediaEncoder.SupportsFilter("bwdif_opencl"))
 3500                {
 03501                    return string.Format(
 03502                        CultureInfo.InvariantCulture,
 03503                        "{0}_opencl={1}:-1:0",
 03504                        useBwdif ? "bwdif" : "yadif",
 03505                        doubleRateDeint ? "1" : "0");
 3506                }
 3507            }
 3508
 03509            if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
 3510            {
 03511                return string.Format(
 03512                    CultureInfo.InvariantCulture,
 03513                    "deinterlace_vaapi=rate={0}",
 03514                    doubleRateDeint ? "field" : "frame");
 3515            }
 3516
 03517            if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
 3518            {
 03519                return "deinterlace_qsv=mode=2";
 3520            }
 3521
 03522            if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
 3523            {
 03524                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3525
 03526                return string.Format(
 03527                    CultureInfo.InvariantCulture,
 03528                    "{0}_videotoolbox={1}:-1:0",
 03529                    useBwdif ? "bwdif" : "yadif",
 03530                    doubleRateDeint ? "1" : "0");
 3531            }
 3532
 03533            return string.Empty;
 3534        }
 3535
 3536        private string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat, bool forc
 3537        {
 03538            if (string.IsNullOrEmpty(hwTonemapSuffix))
 3539            {
 03540                return string.Empty;
 3541            }
 3542
 03543            var args = string.Empty;
 03544            var algorithm = options.TonemappingAlgorithm.ToString().ToLowerInvariant();
 03545            var mode = options.TonemappingMode.ToString().ToLowerInvariant();
 03546            var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 03547            var rangeString = range.ToString().ToLowerInvariant();
 3548
 03549            if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
 3550            {
 03551                var doVaVppProcamp = false;
 03552                var procampParams = string.Empty;
 03553                if (options.VppTonemappingBrightness != 0
 03554                    && options.VppTonemappingBrightness >= -100
 03555                    && options.VppTonemappingBrightness <= 100)
 3556                {
 03557                    procampParams += "procamp_vaapi=b={0}";
 03558                    doVaVppProcamp = true;
 3559                }
 3560
 03561                if (options.VppTonemappingContrast > 1
 03562                    && options.VppTonemappingContrast <= 10)
 3563                {
 03564                    procampParams += doVaVppProcamp ? ":c={1}" : "procamp_vaapi=c={1}";
 03565                    doVaVppProcamp = true;
 3566                }
 3567
 03568                args = procampParams + "{2}tonemap_vaapi=format={3}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
 3569
 03570                return string.Format(
 03571                        CultureInfo.InvariantCulture,
 03572                        args,
 03573                        options.VppTonemappingBrightness,
 03574                        options.VppTonemappingContrast,
 03575                        doVaVppProcamp ? "," : string.Empty,
 03576                        videoFormat ?? "nv12");
 3577            }
 3578            else
 3579            {
 03580                args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
 3581
 03582                var useLegacyTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode
 03583                                           && _legacyTonemapModes.Contains(options.TonemappingMode);
 3584
 03585                var useAdvancedTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegAdvancedTonemapMode
 03586                                              && _advancedTonemapModes.Contains(options.TonemappingMode);
 3587
 03588                if (useLegacyTonemapModes || useAdvancedTonemapModes)
 3589                {
 03590                    args += ":tonemap_mode={5}";
 3591                }
 3592
 03593                if (options.TonemappingParam != 0)
 3594                {
 03595                    args += ":param={6}";
 3596                }
 3597
 03598                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3599                {
 03600                    args += ":range={7}";
 3601                }
 3602            }
 3603
 03604            return string.Format(
 03605                    CultureInfo.InvariantCulture,
 03606                    args,
 03607                    hwTonemapSuffix,
 03608                    videoFormat ?? "nv12",
 03609                    algorithm,
 03610                    options.TonemappingPeak,
 03611                    options.TonemappingDesat,
 03612                    mode,
 03613                    options.TonemappingParam,
 03614                    rangeString);
 3615        }
 3616
 3617        private string GetLibplaceboFilter(
 3618            EncodingOptions options,
 3619            string videoFormat,
 3620            bool doTonemap,
 3621            int? videoWidth,
 3622            int? videoHeight,
 3623            int? requestedWidth,
 3624            int? requestedHeight,
 3625            int? requestedMaxWidth,
 3626            int? requestedMaxHeight,
 3627            bool forceFullRange)
 3628        {
 03629            var (outWidth, outHeight) = GetFixedOutputSize(
 03630                videoWidth,
 03631                videoHeight,
 03632                requestedWidth,
 03633                requestedHeight,
 03634                requestedMaxWidth,
 03635                requestedMaxHeight);
 3636
 03637            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03638            var isSizeFixed = !videoWidth.HasValue
 03639                || outWidth.Value != videoWidth.Value
 03640                || !videoHeight.HasValue
 03641                || outHeight.Value != videoHeight.Value;
 3642
 03643            var sizeArg = isSizeFixed ? (":w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty;
 03644            var formatArg = isFormatFixed ? (":format=" + videoFormat) : string.Empty;
 03645            var tonemapArg = string.Empty;
 3646
 03647            if (doTonemap)
 3648            {
 03649                var algorithm = options.TonemappingAlgorithm;
 03650                var algorithmString = "clip";
 03651                var mode = options.TonemappingMode;
 03652                var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 3653
 03654                if (algorithm == TonemappingAlgorithm.bt2390)
 3655                {
 03656                    algorithmString = "bt.2390";
 3657                }
 03658                else if (algorithm != TonemappingAlgorithm.none)
 3659                {
 03660                    algorithmString = algorithm.ToString().ToLowerInvariant();
 3661                }
 3662
 03663                tonemapArg = $":tonemapping={algorithmString}:peak_detect=0:color_primaries=bt709:color_trc=bt709:colors
 3664
 03665                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3666                {
 03667                    tonemapArg += ":range=" + range.ToString().ToLowerInvariant();
 3668                }
 3669            }
 3670
 03671            return string.Format(
 03672                CultureInfo.InvariantCulture,
 03673                "libplacebo=upscaler=none:downscaler=none{0}{1}{2}",
 03674                sizeArg,
 03675                formatArg,
 03676                tonemapArg);
 3677        }
 3678
 3679        public string GetVideoTransposeDirection(EncodingJobInfo state)
 3680        {
 03681            return (state.VideoStream?.Rotation ?? 0) switch
 03682            {
 03683                90 => "cclock",
 03684                180 => "reversal",
 03685                -90 => "clock",
 03686                -180 => "reversal",
 03687                _ => string.Empty
 03688            };
 3689        }
 3690
 3691        /// <summary>
 3692        /// Gets the parameter of software filter chain.
 3693        /// </summary>
 3694        /// <param name="state">Encoding state.</param>
 3695        /// <param name="options">Encoding options.</param>
 3696        /// <param name="vidEncoder">Video encoder to use.</param>
 3697        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3698        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetSwVidFilterChain(
 3699            EncodingJobInfo state,
 3700            EncodingOptions options,
 3701            string vidEncoder)
 3702        {
 03703            var inW = state.VideoStream?.Width;
 03704            var inH = state.VideoStream?.Height;
 03705            var reqW = state.BaseRequest.Width;
 03706            var reqH = state.BaseRequest.Height;
 03707            var reqMaxW = state.BaseRequest.MaxWidth;
 03708            var reqMaxH = state.BaseRequest.MaxHeight;
 03709            var threeDFormat = state.MediaSource.Video3DFormat;
 3710
 03711            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03712            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03713            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 03714            var isV4l2Encoder = vidEncoder.Contains("h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 3715
 03716            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03717            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03718            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03719            var doToneMap = IsSwTonemapAvailable(state, options);
 03720            var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI;
 3721
 03722            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03723            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03724            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 3725
 03726            var rotation = state.VideoStream?.Rotation ?? 0;
 03727            var swapWAndH = Math.Abs(rotation) == 90;
 03728            var swpInW = swapWAndH ? inH : inW;
 03729            var swpInH = swapWAndH ? inW : inH;
 3730
 3731            /* Make main filters for video stream */
 03732            var mainFilters = new List<string>();
 3733
 03734            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doToneMap));
 3735
 3736            // INPUT sw surface(memory/copy-back from vram)
 3737            // sw deint
 03738            if (doDeintH2645)
 3739            {
 03740                var deintFilter = GetSwDeinterlaceFilter(state, options);
 03741                mainFilters.Add(deintFilter);
 3742            }
 3743
 03744            var outFormat = isSwDecoder ? "yuv420p" : "nv12";
 03745            var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, r
 03746            if (isVaapiEncoder)
 3747            {
 03748                outFormat = "nv12";
 3749            }
 03750            else if (isV4l2Encoder)
 3751            {
 03752                outFormat = "yuv420p";
 3753            }
 3754
 3755            // sw scale
 03756            mainFilters.Add(swScaleFilter);
 3757
 3758            // sw tonemap
 03759            if (doToneMap)
 3760            {
 3761                // tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary
 03762                var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat;
 03763                var tonemapArgString = "tonemapx=tonemap={0}:desat={1}:peak={2}:t=bt709:m=bt709:p=bt709:format={3}";
 3764
 03765                if (options.TonemappingParam != 0)
 3766                {
 03767                    tonemapArgString += ":param={4}";
 3768                }
 3769
 03770                var range = options.TonemappingRange;
 03771                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3772                {
 03773                    tonemapArgString += ":range={5}";
 3774                }
 3775
 03776                var tonemapArgs = string.Format(
 03777                    CultureInfo.InvariantCulture,
 03778                    tonemapArgString,
 03779                    options.TonemappingAlgorithm,
 03780                    options.TonemappingDesat,
 03781                    options.TonemappingPeak,
 03782                    tonemapFormat,
 03783                    options.TonemappingParam,
 03784                    options.TonemappingRange);
 3785
 03786                mainFilters.Add(tonemapArgs);
 3787            }
 3788            else
 3789            {
 3790                // OUTPUT yuv420p/nv12 surface(memory)
 03791                mainFilters.Add("format=" + outFormat);
 3792            }
 3793
 3794            /* Make sub and overlay filters for subtitle stream */
 03795            var subFilters = new List<string>();
 03796            var overlayFilters = new List<string>();
 03797            if (hasTextSubs)
 3798            {
 3799                // subtitles=f='*.ass':alpha=0
 03800                var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03801                mainFilters.Add(textSubtitlesFilter);
 3802            }
 03803            else if (hasGraphicalSubs)
 3804            {
 03805                var subW = state.SubtitleStream?.Width;
 03806                var subH = state.SubtitleStream?.Height;
 03807                var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW
 03808                subFilters.Add(subPreProcFilters);
 03809                overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 3810            }
 3811
 03812            return (mainFilters, subFilters, overlayFilters);
 3813        }
 3814
 3815        /// <summary>
 3816        /// Gets the parameter of Nvidia NVENC filter chain.
 3817        /// </summary>
 3818        /// <param name="state">Encoding state.</param>
 3819        /// <param name="options">Encoding options.</param>
 3820        /// <param name="vidEncoder">Video encoder to use.</param>
 3821        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3822        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFilterChain(
 3823            EncodingJobInfo state,
 3824            EncodingOptions options,
 3825            string vidEncoder)
 3826        {
 03827            if (options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 3828            {
 03829                return (null, null, null);
 3830            }
 3831
 03832            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03833            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03834            var isSwEncoder = !vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 3835
 3836            // legacy cuvid pipeline(copy-back)
 03837            if ((isSwDecoder && isSwEncoder)
 03838                || !IsCudaFullSupported()
 03839                || !_mediaEncoder.SupportsFilter("alphasrc"))
 3840            {
 03841                return GetSwVidFilterChain(state, options, vidEncoder);
 3842            }
 3843
 3844            // preferred nvdec/cuvid + cuda filters + nvenc pipeline
 03845            return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 3846        }
 3847
 3848        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFiltersPrefe
 3849            EncodingJobInfo state,
 3850            EncodingOptions options,
 3851            string vidDecoder,
 3852            string vidEncoder)
 3853        {
 03854            var inW = state.VideoStream?.Width;
 03855            var inH = state.VideoStream?.Height;
 03856            var reqW = state.BaseRequest.Width;
 03857            var reqH = state.BaseRequest.Height;
 03858            var reqMaxW = state.BaseRequest.MaxWidth;
 03859            var reqMaxH = state.BaseRequest.MaxHeight;
 03860            var threeDFormat = state.MediaSource.Video3DFormat;
 3861
 03862            var isNvDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 03863            var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 03864            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03865            var isSwEncoder = !isNvencEncoder;
 03866            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 03867            var isCuInCuOut = isNvDecoder && isNvencEncoder;
 3868
 03869            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03870            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03871            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03872            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03873            var doCuTonemap = IsHwTonemapAvailable(state, options);
 3874
 03875            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03876            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03877            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 03878            var hasAssSubs = hasSubs
 03879                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 03880                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 03881            var subW = state.SubtitleStream?.Width;
 03882            var subH = state.SubtitleStream?.Height;
 3883
 03884            var rotation = state.VideoStream?.Rotation ?? 0;
 03885            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 03886            var doCuTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_cuda");
 03887            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isNvDecoder && doCuTranspose));
 03888            var swpInW = swapWAndH ? inH : inW;
 03889            var swpInH = swapWAndH ? inW : inH;
 3890
 3891            /* Make main filters for video stream */
 03892            var mainFilters = new List<string>();
 3893
 03894            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doCuTonemap));
 3895
 03896            if (isSwDecoder)
 3897            {
 3898                // INPUT sw surface(memory)
 3899                // sw deint
 03900                if (doDeintH2645)
 3901                {
 03902                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 03903                    mainFilters.Add(swDeintFilter);
 3904                }
 3905
 03906                var outFormat = doCuTonemap ? "yuv420p10le" : "yuv420p";
 03907                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 3908                // sw scale
 03909                mainFilters.Add(swScaleFilter);
 03910                mainFilters.Add($"format={outFormat}");
 3911
 3912                // sw => hw
 03913                if (doCuTonemap)
 3914                {
 03915                    mainFilters.Add("hwupload=derive_device=cuda");
 3916                }
 3917            }
 3918
 03919            if (isNvDecoder)
 3920            {
 3921                // INPUT cuda surface(vram)
 3922                // hw deint
 03923                if (doDeintH2645)
 3924                {
 03925                    var deintFilter = GetHwDeinterlaceFilter(state, options, "cuda");
 03926                    mainFilters.Add(deintFilter);
 3927                }
 3928
 3929                // hw transpose
 03930                if (doCuTranspose)
 3931                {
 03932                    mainFilters.Add($"transpose_cuda=dir={transposeDir}");
 3933                }
 3934
 03935                var isRext = IsVideoStreamHevcRext(state);
 03936                var outFormat = doCuTonemap ? (isRext ? "p010" : string.Empty) : "yuv420p";
 03937                var hwScaleFilter = GetHwScaleFilter("scale", "cuda", outFormat, false, swpInW, swpInH, reqW, reqH, reqM
 3938                // hw scale
 03939                mainFilters.Add(hwScaleFilter);
 3940            }
 3941
 3942            // hw tonemap
 03943            if (doCuTonemap)
 3944            {
 03945                var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p", isMjpegEncoder);
 03946                mainFilters.Add(tonemapFilter);
 3947            }
 3948
 03949            var memoryOutput = false;
 03950            var isUploadForCuTonemap = isSwDecoder && doCuTonemap;
 03951            if ((isNvDecoder && isSwEncoder) || (isUploadForCuTonemap && hasSubs))
 3952            {
 03953                memoryOutput = true;
 3954
 3955                // OUTPUT yuv420p surface(memory)
 03956                mainFilters.Add("hwdownload");
 03957                mainFilters.Add("format=yuv420p");
 3958            }
 3959
 3960            // OUTPUT yuv420p surface(memory)
 03961            if (isSwDecoder && isNvencEncoder && !isUploadForCuTonemap)
 3962            {
 03963                memoryOutput = true;
 3964            }
 3965
 03966            if (memoryOutput)
 3967            {
 3968                // text subtitles
 03969                if (hasTextSubs)
 3970                {
 03971                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03972                    mainFilters.Add(textSubtitlesFilter);
 3973                }
 3974            }
 3975
 3976            // OUTPUT cuda(yuv420p) surface(vram)
 3977
 3978            /* Make sub and overlay filters for subtitle stream */
 03979            var subFilters = new List<string>();
 03980            var overlayFilters = new List<string>();
 03981            if (isCuInCuOut)
 3982            {
 03983                if (hasSubs)
 3984                {
 03985                    var alphaFormatOpt = string.Empty;
 03986                    if (hasGraphicalSubs)
 3987                    {
 03988                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 03989                        subFilters.Add(subPreProcFilters);
 03990                        subFilters.Add("format=yuva420p");
 3991                    }
 03992                    else if (hasTextSubs)
 3993                    {
 03994                        var framerate = state.VideoStream?.RealFrameRate;
 03995                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 3996
 3997                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 03998                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 03999                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04000                        subFilters.Add(alphaSrcFilter);
 04001                        subFilters.Add("format=yuva420p");
 04002                        subFilters.Add(subTextSubtitlesFilter);
 4003
 04004                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayCudaAlphaFormat)
 04005                            ? ":alpha_format=premultiplied" : string.Empty;
 4006                    }
 4007
 04008                    subFilters.Add("hwupload=derive_device=cuda");
 04009                    overlayFilters.Add($"overlay_cuda=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 4010                }
 4011            }
 4012            else
 4013            {
 04014                if (hasGraphicalSubs)
 4015                {
 04016                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04017                    subFilters.Add(subPreProcFilters);
 04018                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4019                }
 4020            }
 4021
 04022            return (mainFilters, subFilters, overlayFilters);
 4023        }
 4024
 4025        /// <summary>
 4026        /// Gets the parameter of AMD AMF filter chain.
 4027        /// </summary>
 4028        /// <param name="state">Encoding state.</param>
 4029        /// <param name="options">Encoding options.</param>
 4030        /// <param name="vidEncoder">Video encoder to use.</param>
 4031        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4032        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVidFilterChain(
 4033            EncodingJobInfo state,
 4034            EncodingOptions options,
 4035            string vidEncoder)
 4036        {
 04037            if (options.HardwareAccelerationType != HardwareAccelerationType.amf)
 4038            {
 04039                return (null, null, null);
 4040            }
 4041
 04042            var isWindows = OperatingSystem.IsWindows();
 04043            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04044            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04045            var isSwEncoder = !vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04046            var isAmfDx11OclSupported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va") && IsOpenclFullSupported()
 4047
 4048            // legacy d3d11va pipeline(copy-back)
 04049            if ((isSwDecoder && isSwEncoder)
 04050                || !isAmfDx11OclSupported
 04051                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4052            {
 04053                return GetSwVidFilterChain(state, options, vidEncoder);
 4054            }
 4055
 4056            // preferred d3d11va + opencl filters + amf pipeline
 04057            return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4058        }
 4059
 4060        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdDx11VidFiltersPref
 4061            EncodingJobInfo state,
 4062            EncodingOptions options,
 4063            string vidDecoder,
 4064            string vidEncoder)
 4065        {
 04066            var inW = state.VideoStream?.Width;
 04067            var inH = state.VideoStream?.Height;
 04068            var reqW = state.BaseRequest.Width;
 04069            var reqH = state.BaseRequest.Height;
 04070            var reqMaxW = state.BaseRequest.MaxWidth;
 04071            var reqMaxH = state.BaseRequest.MaxHeight;
 04072            var threeDFormat = state.MediaSource.Video3DFormat;
 4073
 04074            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04075            var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04076            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04077            var isSwEncoder = !isAmfEncoder;
 04078            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04079            var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder;
 4080
 04081            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04082            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04083            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04084            var doOclTonemap = IsHwTonemapAvailable(state, options);
 4085
 04086            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04087            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04088            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04089            var hasAssSubs = hasSubs
 04090                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04091                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04092            var subW = state.SubtitleStream?.Width;
 04093            var subH = state.SubtitleStream?.Height;
 4094
 04095            var rotation = state.VideoStream?.Rotation ?? 0;
 04096            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04097            var doOclTranspose = !string.IsNullOrEmpty(transposeDir)
 04098                && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TransposeOpenclReversal);
 04099            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isD3d11vaDecoder && doOclTranspose));
 04100            var swpInW = swapWAndH ? inH : inW;
 04101            var swpInH = swapWAndH ? inW : inH;
 4102
 4103            /* Make main filters for video stream */
 04104            var mainFilters = new List<string>();
 4105
 04106            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 4107
 04108            if (isSwDecoder)
 4109            {
 4110                // INPUT sw surface(memory)
 4111                // sw deint
 04112                if (doDeintH2645)
 4113                {
 04114                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04115                    mainFilters.Add(swDeintFilter);
 4116                }
 4117
 04118                var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
 04119                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 4120                // sw scale
 04121                mainFilters.Add(swScaleFilter);
 04122                mainFilters.Add($"format={outFormat}");
 4123
 4124                // keep video at memory except ocl tonemap,
 4125                // since the overhead caused by hwupload >>> using sw filter.
 4126                // sw => hw
 04127                if (doOclTonemap)
 4128                {
 04129                    mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=24");
 04130                    mainFilters.Add("format=d3d11");
 04131                    mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4132                }
 4133            }
 4134
 04135            if (isD3d11vaDecoder)
 4136            {
 4137                // INPUT d3d11 surface(vram)
 4138                // map from d3d11va to opencl via d3d11-opencl interop.
 04139                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4140
 4141                // hw deint
 04142                if (doDeintH2645)
 4143                {
 04144                    var deintFilter = GetHwDeinterlaceFilter(state, options, "opencl");
 04145                    mainFilters.Add(deintFilter);
 4146                }
 4147
 4148                // hw transpose
 04149                if (doOclTranspose)
 4150                {
 04151                    mainFilters.Add($"transpose_opencl=dir={transposeDir}");
 4152                }
 4153
 04154                var outFormat = doOclTonemap ? string.Empty : "nv12";
 04155                var hwScaleFilter = GetHwScaleFilter("scale", "opencl", outFormat, false, swpInW, swpInH, reqW, reqH, re
 4156                // hw scale
 04157                mainFilters.Add(hwScaleFilter);
 4158            }
 4159
 4160            // hw tonemap
 04161            if (doOclTonemap)
 4162            {
 04163                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04164                mainFilters.Add(tonemapFilter);
 4165            }
 4166
 04167            var memoryOutput = false;
 04168            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04169            if (isD3d11vaDecoder && isSwEncoder)
 4170            {
 04171                memoryOutput = true;
 4172
 4173                // OUTPUT nv12 surface(memory)
 4174                // prefer hwmap to hwdownload on opencl.
 04175                var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap=mode=read";
 04176                mainFilters.Add(hwTransferFilter);
 04177                mainFilters.Add("format=nv12");
 4178            }
 4179
 4180            // OUTPUT yuv420p surface
 04181            if (isSwDecoder && isAmfEncoder && !isUploadForOclTonemap)
 4182            {
 04183                memoryOutput = true;
 4184            }
 4185
 04186            if (memoryOutput)
 4187            {
 4188                // text subtitles
 04189                if (hasTextSubs)
 4190                {
 04191                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04192                    mainFilters.Add(textSubtitlesFilter);
 4193                }
 4194            }
 4195
 04196            if ((isDxInDxOut || isUploadForOclTonemap) && !hasSubs)
 4197            {
 4198                // OUTPUT d3d11(nv12) surface(vram)
 4199                // reverse-mapping via d3d11-opencl interop.
 04200                mainFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04201                mainFilters.Add("format=d3d11");
 4202            }
 4203
 4204            /* Make sub and overlay filters for subtitle stream */
 04205            var subFilters = new List<string>();
 04206            var overlayFilters = new List<string>();
 04207            if (isDxInDxOut || isUploadForOclTonemap)
 4208            {
 04209                if (hasSubs)
 4210                {
 04211                    var alphaFormatOpt = string.Empty;
 04212                    if (hasGraphicalSubs)
 4213                    {
 04214                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04215                        subFilters.Add(subPreProcFilters);
 04216                        subFilters.Add("format=yuva420p");
 4217                    }
 04218                    else if (hasTextSubs)
 4219                    {
 04220                        var framerate = state.VideoStream?.RealFrameRate;
 04221                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4222
 4223                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04224                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04225                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04226                        subFilters.Add(alphaSrcFilter);
 04227                        subFilters.Add("format=yuva420p");
 04228                        subFilters.Add(subTextSubtitlesFilter);
 4229
 04230                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclAlphaForma
 04231                            ? ":alpha_format=premultiplied" : string.Empty;
 4232                    }
 4233
 04234                    subFilters.Add("hwupload=derive_device=opencl");
 04235                    overlayFilters.Add($"overlay_opencl=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 04236                    overlayFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04237                    overlayFilters.Add("format=d3d11");
 4238                }
 4239            }
 04240            else if (memoryOutput)
 4241            {
 04242                if (hasGraphicalSubs)
 4243                {
 04244                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04245                    subFilters.Add(subPreProcFilters);
 04246                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4247                }
 4248            }
 4249
 04250            return (mainFilters, subFilters, overlayFilters);
 4251        }
 4252
 4253        /// <summary>
 4254        /// Gets the parameter of Intel QSV filter chain.
 4255        /// </summary>
 4256        /// <param name="state">Encoding state.</param>
 4257        /// <param name="options">Encoding options.</param>
 4258        /// <param name="vidEncoder">Video encoder to use.</param>
 4259        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4260        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVidFilterChain(
 4261            EncodingJobInfo state,
 4262            EncodingOptions options,
 4263            string vidEncoder)
 4264        {
 04265            if (options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 4266            {
 04267                return (null, null, null);
 4268            }
 4269
 04270            var isWindows = OperatingSystem.IsWindows();
 04271            var isLinux = OperatingSystem.IsLinux();
 04272            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04273            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04274            var isSwEncoder = !vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04275            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 04276            var isIntelDx11OclSupported = isWindows
 04277                && _mediaEncoder.SupportsHwaccel("d3d11va")
 04278                && isQsvOclSupported;
 04279            var isIntelVaapiOclSupported = isLinux
 04280                && IsVaapiSupported(state)
 04281                && isQsvOclSupported;
 4282
 4283            // legacy qsv pipeline(copy-back)
 04284            if ((isSwDecoder && isSwEncoder)
 04285                || (!isIntelVaapiOclSupported && !isIntelDx11OclSupported)
 04286                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4287            {
 04288                return GetSwVidFilterChain(state, options, vidEncoder);
 4289            }
 4290
 4291            // preferred qsv(vaapi) + opencl filters pipeline
 04292            if (isIntelVaapiOclSupported)
 4293            {
 04294                return GetIntelQsvVaapiVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4295            }
 4296
 4297            // preferred qsv(d3d11) + opencl filters pipeline
 04298            if (isIntelDx11OclSupported)
 4299            {
 04300                return GetIntelQsvDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4301            }
 4302
 04303            return (null, null, null);
 4304        }
 4305
 4306        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvDx11VidFilter
 4307            EncodingJobInfo state,
 4308            EncodingOptions options,
 4309            string vidDecoder,
 4310            string vidEncoder)
 4311        {
 04312            var inW = state.VideoStream?.Width;
 04313            var inH = state.VideoStream?.Height;
 04314            var reqW = state.BaseRequest.Width;
 04315            var reqH = state.BaseRequest.Height;
 04316            var reqMaxW = state.BaseRequest.MaxWidth;
 04317            var reqMaxH = state.BaseRequest.MaxHeight;
 04318            var threeDFormat = state.MediaSource.Video3DFormat;
 4319
 04320            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04321            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04322            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04323            var isHwDecoder = isD3d11vaDecoder || isQsvDecoder;
 04324            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04325            var isSwEncoder = !isQsvEncoder;
 04326            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04327            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4328
 04329            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04330            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04331            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04332            var doVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04333            var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options);
 04334            var doTonemap = doVppTonemap || doOclTonemap;
 4335
 04336            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04337            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04338            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04339            var hasAssSubs = hasSubs
 04340                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04341                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04342            var subW = state.SubtitleStream?.Width;
 04343            var subH = state.SubtitleStream?.Height;
 4344
 04345            var rotation = state.VideoStream?.Rotation ?? 0;
 04346            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04347            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04348            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isD3d11vaDecoder || isQsvDecoder) && doVppTran
 04349            var swpInW = swapWAndH ? inH : inW;
 04350            var swpInH = swapWAndH ? inW : inH;
 4351
 4352            /* Make main filters for video stream */
 04353            var mainFilters = new List<string>();
 4354
 04355            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4356
 04357            if (isSwDecoder)
 4358            {
 4359                // INPUT sw surface(memory)
 4360                // sw deint
 04361                if (doDeintH2645)
 4362                {
 04363                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04364                    mainFilters.Add(swDeintFilter);
 4365                }
 4366
 04367                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04368                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04369                if (isMjpegEncoder && !doOclTonemap)
 4370                {
 4371                    // sw decoder + hw mjpeg encoder
 04372                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4373                }
 4374
 4375                // sw scale
 04376                mainFilters.Add(swScaleFilter);
 04377                mainFilters.Add($"format={outFormat}");
 4378
 4379                // keep video at memory except ocl tonemap,
 4380                // since the overhead caused by hwupload >>> using sw filter.
 4381                // sw => hw
 04382                if (doOclTonemap)
 4383                {
 04384                    mainFilters.Add("hwupload=derive_device=opencl");
 4385                }
 4386            }
 04387            else if (isD3d11vaDecoder || isQsvDecoder)
 4388            {
 04389                var isRext = IsVideoStreamHevcRext(state);
 04390                var twoPassVppTonemap = false;
 04391                var doVppFullRangeOut = isMjpegEncoder
 04392                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04393                var doVppScaleModeHq = isMjpegEncoder
 04394                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 04395                var doVppProcamp = false;
 04396                var procampParams = string.Empty;
 04397                var procampParamsString = string.Empty;
 04398                if (doVppTonemap)
 4399                {
 04400                    if (isRext)
 4401                    {
 4402                        // VPP tonemap requires p010 input
 04403                        twoPassVppTonemap = true;
 4404                    }
 4405
 04406                    if (options.VppTonemappingBrightness != 0
 04407                        && options.VppTonemappingBrightness >= -100
 04408                        && options.VppTonemappingBrightness <= 100)
 4409                    {
 04410                        procampParamsString += ":brightness={0}";
 04411                        twoPassVppTonemap = doVppProcamp = true;
 4412                    }
 4413
 04414                    if (options.VppTonemappingContrast > 1
 04415                        && options.VppTonemappingContrast <= 10)
 4416                    {
 04417                        procampParamsString += ":contrast={1}";
 04418                        twoPassVppTonemap = doVppProcamp = true;
 4419                    }
 4420
 04421                    if (doVppProcamp)
 4422                    {
 04423                        procampParamsString += ":procamp=1:async_depth=2";
 04424                        procampParams = string.Format(
 04425                            CultureInfo.InvariantCulture,
 04426                            procampParamsString,
 04427                            options.VppTonemappingBrightness,
 04428                            options.VppTonemappingContrast);
 4429                    }
 4430                }
 4431
 04432                var outFormat = doOclTonemap ? ((doVppTranspose || isRext) ? "p010" : string.Empty) : "nv12";
 04433                outFormat = twoPassVppTonemap ? "p010" : outFormat;
 4434
 04435                var swapOutputWandH = doVppTranspose && swapWAndH;
 04436                var hwScaleFilter = GetHwScaleFilter("vpp", "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, req
 4437
 04438                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose)
 4439                {
 04440                    hwScaleFilter += $":transpose={transposeDir}";
 4441                }
 4442
 04443                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4444                {
 04445                    hwScaleFilter += (doVppFullRangeOut && !doOclTonemap) ? ":out_range=pc" : string.Empty;
 04446                    hwScaleFilter += doVppScaleModeHq ? ":scale_mode=hq" : string.Empty;
 4447                }
 4448
 04449                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTonemap)
 4450                {
 04451                    hwScaleFilter += doVppProcamp ? procampParams : (twoPassVppTonemap ? string.Empty : ":tonemap=1");
 4452                }
 4453
 04454                if (isD3d11vaDecoder)
 4455                {
 04456                    if (!string.IsNullOrEmpty(hwScaleFilter) || doDeintH2645)
 4457                    {
 4458                        // INPUT d3d11 surface(vram)
 4459                        // map from d3d11va to qsv.
 04460                        mainFilters.Add("hwmap=derive_device=qsv");
 4461                    }
 4462                }
 4463
 4464                // hw deint
 04465                if (doDeintH2645)
 4466                {
 04467                    var deintFilter = GetHwDeinterlaceFilter(state, options, "qsv");
 04468                    mainFilters.Add(deintFilter);
 4469                }
 4470
 4471                // hw transpose & scale & tonemap(w/o procamp)
 04472                mainFilters.Add(hwScaleFilter);
 4473
 4474                // hw tonemap(w/ procamp)
 04475                if (doVppTonemap && twoPassVppTonemap)
 4476                {
 04477                    mainFilters.Add("vpp_qsv=tonemap=1:format=nv12:async_depth=2");
 4478                }
 4479
 4480                // force bt709 just in case vpp tonemap is not triggered or using MSDK instead of VPL.
 04481                if (doVppTonemap)
 4482                {
 04483                    mainFilters.Add(GetOverwriteColorPropertiesParam(state, false));
 4484                }
 4485            }
 4486
 04487            if (doOclTonemap && isHwDecoder)
 4488            {
 4489                // map from qsv to opencl via qsv(d3d11)-opencl interop.
 04490                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4491            }
 4492
 4493            // hw tonemap
 04494            if (doOclTonemap)
 4495            {
 04496                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04497                mainFilters.Add(tonemapFilter);
 4498            }
 4499
 04500            var memoryOutput = false;
 04501            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04502            var isHwmapUsable = isSwEncoder && doOclTonemap;
 04503            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4504            {
 04505                memoryOutput = true;
 4506
 4507                // OUTPUT nv12 surface(memory)
 4508                // prefer hwmap to hwdownload on opencl.
 4509                // qsv hwmap is not fully implemented for the time being.
 04510                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04511                mainFilters.Add("format=nv12");
 4512            }
 4513
 4514            // OUTPUT nv12 surface(memory)
 04515            if (isSwDecoder && isQsvEncoder)
 4516            {
 04517                memoryOutput = true;
 4518            }
 4519
 04520            if (memoryOutput)
 4521            {
 4522                // text subtitles
 04523                if (hasTextSubs)
 4524                {
 04525                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04526                    mainFilters.Add(textSubtitlesFilter);
 4527                }
 4528            }
 4529
 04530            if (isQsvInQsvOut && doOclTonemap)
 4531            {
 4532                // OUTPUT qsv(nv12) surface(vram)
 4533                // reverse-mapping via qsv(d3d11)-opencl interop.
 04534                mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1");
 04535                mainFilters.Add("format=qsv");
 4536            }
 4537
 4538            /* Make sub and overlay filters for subtitle stream */
 04539            var subFilters = new List<string>();
 04540            var overlayFilters = new List<string>();
 04541            if (isQsvInQsvOut)
 4542            {
 04543                if (hasSubs)
 4544                {
 04545                    if (hasGraphicalSubs)
 4546                    {
 4547                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04548                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04549                        subFilters.Add(subPreProcFilters);
 04550                        subFilters.Add("format=bgra");
 4551                    }
 04552                    else if (hasTextSubs)
 4553                    {
 04554                        var framerate = state.VideoStream?.RealFrameRate;
 04555                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4556
 4557                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 04558                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04559                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04560                        subFilters.Add(alphaSrcFilter);
 04561                        subFilters.Add("format=bgra");
 04562                        subFilters.Add(subTextSubtitlesFilter);
 4563                    }
 4564
 4565                    // qsv requires a fixed pool size.
 4566                    // default to 64 otherwise it will fail on certain iGPU.
 04567                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4568
 04569                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04570                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04571                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04572                        : string.Empty;
 04573                    var overlayQsvFilter = string.Format(
 04574                        CultureInfo.InvariantCulture,
 04575                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04576                        overlaySize);
 04577                    overlayFilters.Add(overlayQsvFilter);
 4578                }
 4579            }
 04580            else if (memoryOutput)
 4581            {
 04582                if (hasGraphicalSubs)
 4583                {
 04584                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04585                    subFilters.Add(subPreProcFilters);
 04586                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4587                }
 4588            }
 4589
 04590            return (mainFilters, subFilters, overlayFilters);
 4591        }
 4592
 4593        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvVaapiVidFilte
 4594            EncodingJobInfo state,
 4595            EncodingOptions options,
 4596            string vidDecoder,
 4597            string vidEncoder)
 4598        {
 04599            var inW = state.VideoStream?.Width;
 04600            var inH = state.VideoStream?.Height;
 04601            var reqW = state.BaseRequest.Width;
 04602            var reqH = state.BaseRequest.Height;
 04603            var reqMaxW = state.BaseRequest.MaxWidth;
 04604            var reqMaxH = state.BaseRequest.MaxHeight;
 04605            var threeDFormat = state.MediaSource.Video3DFormat;
 4606
 04607            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04608            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04609            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04610            var isHwDecoder = isVaapiDecoder || isQsvDecoder;
 04611            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04612            var isSwEncoder = !isQsvEncoder;
 04613            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04614            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4615
 04616            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04617            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04618            var doVaVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04619            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04620            var doTonemap = doVaVppTonemap || doOclTonemap;
 04621            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4622
 04623            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04624            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04625            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04626            var hasAssSubs = hasSubs
 04627                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04628                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04629            var subW = state.SubtitleStream?.Width;
 04630            var subH = state.SubtitleStream?.Height;
 4631
 04632            var rotation = state.VideoStream?.Rotation ?? 0;
 04633            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04634            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04635            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isVaapiDecoder || isQsvDecoder) && doVppTransp
 04636            var swpInW = swapWAndH ? inH : inW;
 04637            var swpInH = swapWAndH ? inW : inH;
 4638
 4639            /* Make main filters for video stream */
 04640            var mainFilters = new List<string>();
 4641
 04642            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4643
 04644            if (isSwDecoder)
 4645            {
 4646                // INPUT sw surface(memory)
 4647                // sw deint
 04648                if (doDeintH2645)
 4649                {
 04650                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04651                    mainFilters.Add(swDeintFilter);
 4652                }
 4653
 04654                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04655                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04656                if (isMjpegEncoder && !doOclTonemap)
 4657                {
 4658                    // sw decoder + hw mjpeg encoder
 04659                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4660                }
 4661
 4662                // sw scale
 04663                mainFilters.Add(swScaleFilter);
 04664                mainFilters.Add($"format={outFormat}");
 4665
 4666                // keep video at memory except ocl tonemap,
 4667                // since the overhead caused by hwupload >>> using sw filter.
 4668                // sw => hw
 04669                if (doOclTonemap)
 4670                {
 04671                    mainFilters.Add("hwupload=derive_device=opencl");
 4672                }
 4673            }
 04674            else if (isVaapiDecoder || isQsvDecoder)
 4675            {
 04676                var hwFilterSuffix = isVaapiDecoder ? "vaapi" : "qsv";
 04677                var isRext = IsVideoStreamHevcRext(state);
 04678                var doVppFullRangeOut = isMjpegEncoder
 04679                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04680                var doVppScaleModeHq = isMjpegEncoder
 04681                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 4682
 4683                // INPUT vaapi/qsv surface(vram)
 4684                // hw deint
 04685                if (doDeintH2645)
 4686                {
 04687                    var deintFilter = GetHwDeinterlaceFilter(state, options, hwFilterSuffix);
 04688                    mainFilters.Add(deintFilter);
 4689                }
 4690
 4691                // hw transpose(vaapi vpp)
 04692                if (isVaapiDecoder && doVppTranspose)
 4693                {
 04694                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 4695                }
 4696
 04697                var outFormat = doTonemap ? (((isQsvDecoder && doVppTranspose) || isRext) ? "p010" : string.Empty) : "nv
 04698                var swapOutputWandH = isQsvDecoder && doVppTranspose && swapWAndH;
 04699                var hwScalePrefix = isQsvDecoder ? "vpp" : "scale";
 04700                var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, hwFilterSuffix, outFormat, swapOutputWandH, swpInW, 
 4701
 04702                if (!string.IsNullOrEmpty(hwScaleFilter) && isQsvDecoder && doVppTranspose)
 4703                {
 04704                    hwScaleFilter += $":transpose={transposeDir}";
 4705                }
 4706
 04707                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4708                {
 04709                    hwScaleFilter += ((isQsvDecoder && !doVppFullRangeOut) || doOclTonemap) ? string.Empty : ":out_range
 04710                    hwScaleFilter += isQsvDecoder ? (doVppScaleModeHq ? ":scale_mode=hq" : string.Empty) : ":mode=hq";
 4711                }
 4712
 4713                // allocate extra pool sizes for vaapi vpp scale
 04714                if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder)
 4715                {
 04716                    hwScaleFilter += ":extra_hw_frames=24";
 4717                }
 4718
 4719                // hw transpose(qsv vpp) & scale
 04720                mainFilters.Add(hwScaleFilter);
 4721            }
 4722
 4723            // vaapi vpp tonemap
 04724            if (doVaVppTonemap && isHwDecoder)
 4725            {
 04726                if (isQsvDecoder)
 4727                {
 4728                    // map from qsv to vaapi.
 04729                    mainFilters.Add("hwmap=derive_device=vaapi");
 04730                    mainFilters.Add("format=vaapi");
 4731                }
 4732
 04733                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 04734                mainFilters.Add(tonemapFilter);
 4735
 04736                if (isQsvDecoder)
 4737                {
 4738                    // map from vaapi to qsv.
 04739                    mainFilters.Add("hwmap=derive_device=qsv");
 04740                    mainFilters.Add("format=qsv");
 4741                }
 4742            }
 4743
 04744            if (doOclTonemap && isHwDecoder)
 4745            {
 4746                // map from qsv to opencl via qsv(vaapi)-opencl interop.
 04747                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4748            }
 4749
 4750            // ocl tonemap
 04751            if (doOclTonemap)
 4752            {
 04753                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04754                mainFilters.Add(tonemapFilter);
 4755            }
 4756
 04757            var memoryOutput = false;
 04758            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04759            var isHwmapUsable = isSwEncoder && (doOclTonemap || isVaapiDecoder);
 04760            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4761            {
 04762                memoryOutput = true;
 4763
 4764                // OUTPUT nv12 surface(memory)
 4765                // prefer hwmap to hwdownload on opencl/vaapi.
 4766                // qsv hwmap is not fully implemented for the time being.
 04767                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04768                mainFilters.Add("format=nv12");
 4769            }
 4770
 4771            // OUTPUT nv12 surface(memory)
 04772            if (isSwDecoder && isQsvEncoder)
 4773            {
 04774                memoryOutput = true;
 4775            }
 4776
 04777            if (memoryOutput)
 4778            {
 4779                // text subtitles
 04780                if (hasTextSubs)
 4781                {
 04782                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04783                    mainFilters.Add(textSubtitlesFilter);
 4784                }
 4785            }
 4786
 04787            if (isQsvInQsvOut)
 4788            {
 04789                if (doOclTonemap)
 4790                {
 4791                    // OUTPUT qsv(nv12) surface(vram)
 4792                    // reverse-mapping via qsv(vaapi)-opencl interop.
 4793                    // add extra pool size to avoid the 'cannot allocate memory' error on hevc_qsv.
 04794                    mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1:extra_hw_frames=16");
 04795                    mainFilters.Add("format=qsv");
 4796                }
 04797                else if (isVaapiDecoder)
 4798                {
 04799                    mainFilters.Add("hwmap=derive_device=qsv");
 04800                    mainFilters.Add("format=qsv");
 4801                }
 4802            }
 4803
 4804            /* Make sub and overlay filters for subtitle stream */
 04805            var subFilters = new List<string>();
 04806            var overlayFilters = new List<string>();
 04807            if (isQsvInQsvOut)
 4808            {
 04809                if (hasSubs)
 4810                {
 04811                    if (hasGraphicalSubs)
 4812                    {
 4813                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04814                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04815                        subFilters.Add(subPreProcFilters);
 04816                        subFilters.Add("format=bgra");
 4817                    }
 04818                    else if (hasTextSubs)
 4819                    {
 04820                        var framerate = state.VideoStream?.RealFrameRate;
 04821                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4822
 04823                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04824                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04825                        subFilters.Add(alphaSrcFilter);
 04826                        subFilters.Add("format=bgra");
 04827                        subFilters.Add(subTextSubtitlesFilter);
 4828                    }
 4829
 4830                    // qsv requires a fixed pool size.
 4831                    // default to 64 otherwise it will fail on certain iGPU.
 04832                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4833
 04834                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04835                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04836                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04837                        : string.Empty;
 04838                    var overlayQsvFilter = string.Format(
 04839                        CultureInfo.InvariantCulture,
 04840                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04841                        overlaySize);
 04842                    overlayFilters.Add(overlayQsvFilter);
 4843                }
 4844            }
 04845            else if (memoryOutput)
 4846            {
 04847                if (hasGraphicalSubs)
 4848                {
 04849                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04850                    subFilters.Add(subPreProcFilters);
 04851                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4852                }
 4853            }
 4854
 04855            return (mainFilters, subFilters, overlayFilters);
 4856        }
 4857
 4858        /// <summary>
 4859        /// Gets the parameter of Intel/AMD VAAPI filter chain.
 4860        /// </summary>
 4861        /// <param name="state">Encoding state.</param>
 4862        /// <param name="options">Encoding options.</param>
 4863        /// <param name="vidEncoder">Video encoder to use.</param>
 4864        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4865        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiVidFilterChain(
 4866            EncodingJobInfo state,
 4867            EncodingOptions options,
 4868            string vidEncoder)
 4869        {
 04870            if (options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 4871            {
 04872                return (null, null, null);
 4873            }
 4874
 04875            var isLinux = OperatingSystem.IsLinux();
 04876            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04877            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04878            var isSwEncoder = !vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04879            var isVaapiFullSupported = isLinux && IsVaapiSupported(state) && IsVaapiFullSupported();
 04880            var isVaapiOclSupported = isVaapiFullSupported && IsOpenclFullSupported();
 04881            var isVaapiVkSupported = isVaapiFullSupported && IsVulkanFullSupported();
 4882
 4883            // legacy vaapi pipeline(copy-back)
 04884            if ((isSwDecoder && isSwEncoder)
 04885                || !isVaapiOclSupported
 04886                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4887            {
 04888                var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
 4889
 04890                if (!isSwEncoder)
 4891                {
 04892                    var newfilters = new List<string>();
 04893                    var noOverlay = swFilterChain.OverlayFilters.Count == 0;
 04894                    newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
 04895                    newfilters.Add("hwupload=derive_device=vaapi");
 4896
 04897                    var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
 04898                    var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
 04899                    return (mainFilters, swFilterChain.SubFilters, overlayFilters);
 4900                }
 4901
 04902                return swFilterChain;
 4903            }
 4904
 4905            // preferred vaapi + opencl filters pipeline
 04906            if (_mediaEncoder.IsVaapiDeviceInteliHD)
 4907            {
 4908                // Intel iHD path, with extra vpp tonemap and overlay support.
 04909                return GetIntelVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4910            }
 4911
 4912            // preferred vaapi + vulkan filters pipeline
 04913            if (_mediaEncoder.IsVaapiDeviceAmd
 04914                && isVaapiVkSupported
 04915                && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 04916                && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 4917            {
 4918                // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support.
 04919                return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4920            }
 4921
 4922            // Intel i965 and Amd legacy driver path, only featuring scale and deinterlace support.
 04923            return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4924        }
 4925
 4926        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVaapiFullVidFilt
 4927            EncodingJobInfo state,
 4928            EncodingOptions options,
 4929            string vidDecoder,
 4930            string vidEncoder)
 4931        {
 04932            var inW = state.VideoStream?.Width;
 04933            var inH = state.VideoStream?.Height;
 04934            var reqW = state.BaseRequest.Width;
 04935            var reqH = state.BaseRequest.Height;
 04936            var reqMaxW = state.BaseRequest.MaxWidth;
 04937            var reqMaxH = state.BaseRequest.MaxHeight;
 04938            var threeDFormat = state.MediaSource.Video3DFormat;
 4939
 04940            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04941            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04942            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04943            var isSwEncoder = !isVaapiEncoder;
 04944            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04945            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 4946
 04947            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04948            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04949            var doVaVppTonemap = isVaapiDecoder && IsIntelVppTonemapAvailable(state, options);
 04950            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04951            var doTonemap = doVaVppTonemap || doOclTonemap;
 04952            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4953
 04954            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04955            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04956            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04957            var hasAssSubs = hasSubs
 04958                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04959                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04960            var subW = state.SubtitleStream?.Width;
 04961            var subH = state.SubtitleStream?.Height;
 4962
 04963            var rotation = state.VideoStream?.Rotation ?? 0;
 04964            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04965            var doVaVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04966            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVaVppTranspose));
 04967            var swpInW = swapWAndH ? inH : inW;
 04968            var swpInH = swapWAndH ? inW : inH;
 4969
 4970            /* Make main filters for video stream */
 04971            var mainFilters = new List<string>();
 4972
 04973            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4974
 04975            if (isSwDecoder)
 4976            {
 4977                // INPUT sw surface(memory)
 4978                // sw deint
 04979                if (doDeintH2645)
 4980                {
 04981                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04982                    mainFilters.Add(swDeintFilter);
 4983                }
 4984
 04985                var outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 04986                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04987                if (isMjpegEncoder && !doOclTonemap)
 4988                {
 4989                    // sw decoder + hw mjpeg encoder
 04990                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4991                }
 4992
 4993                // sw scale
 04994                mainFilters.Add(swScaleFilter);
 04995                mainFilters.Add($"format={outFormat}");
 4996
 4997                // keep video at memory except ocl tonemap,
 4998                // since the overhead caused by hwupload >>> using sw filter.
 4999                // sw => hw
 05000                if (doOclTonemap)
 5001                {
 05002                    mainFilters.Add("hwupload=derive_device=opencl");
 5003                }
 5004            }
 05005            else if (isVaapiDecoder)
 5006            {
 05007                var isRext = IsVideoStreamHevcRext(state);
 5008
 5009                // INPUT vaapi surface(vram)
 5010                // hw deint
 05011                if (doDeintH2645)
 5012                {
 05013                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05014                    mainFilters.Add(deintFilter);
 5015                }
 5016
 5017                // hw transpose
 05018                if (doVaVppTranspose)
 5019                {
 05020                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 5021                }
 5022
 05023                var outFormat = doTonemap ? (isRext ? "p010" : string.Empty) : "nv12";
 05024                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, swpInW, swpInH, reqW, reqH, req
 5025
 05026                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5027                {
 05028                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05029                    hwScaleFilter += ":mode=hq";
 5030                }
 5031
 5032                // allocate extra pool sizes for vaapi vpp
 05033                if (!string.IsNullOrEmpty(hwScaleFilter))
 5034                {
 05035                    hwScaleFilter += ":extra_hw_frames=24";
 5036                }
 5037
 5038                // hw scale
 05039                mainFilters.Add(hwScaleFilter);
 5040            }
 5041
 5042            // vaapi vpp tonemap
 05043            if (doVaVppTonemap && isVaapiDecoder)
 5044            {
 05045                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 05046                mainFilters.Add(tonemapFilter);
 5047            }
 5048
 05049            if (doOclTonemap && isVaapiDecoder)
 5050            {
 5051                // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05052                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 5053            }
 5054
 5055            // ocl tonemap
 05056            if (doOclTonemap)
 5057            {
 05058                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05059                mainFilters.Add(tonemapFilter);
 5060            }
 5061
 05062            if (doOclTonemap && isVaInVaOut)
 5063            {
 5064                // OUTPUT vaapi(nv12) surface(vram)
 5065                // reverse-mapping via vaapi-opencl interop.
 05066                mainFilters.Add("hwmap=derive_device=vaapi:mode=write:reverse=1");
 05067                mainFilters.Add("format=vaapi");
 5068            }
 5069
 05070            var memoryOutput = false;
 05071            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 05072            var isHwmapNotUsable = isUploadForOclTonemap && isVaapiEncoder;
 05073            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap)
 5074            {
 05075                memoryOutput = true;
 5076
 5077                // OUTPUT nv12 surface(memory)
 5078                // prefer hwmap to hwdownload on opencl/vaapi.
 05079                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap=mode=read");
 05080                mainFilters.Add("format=nv12");
 5081            }
 5082
 5083            // OUTPUT nv12 surface(memory)
 05084            if (isSwDecoder && isVaapiEncoder)
 5085            {
 05086                memoryOutput = true;
 5087            }
 5088
 05089            if (memoryOutput)
 5090            {
 5091                // text subtitles
 05092                if (hasTextSubs)
 5093                {
 05094                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05095                    mainFilters.Add(textSubtitlesFilter);
 5096                }
 5097            }
 5098
 05099            if (memoryOutput && isVaapiEncoder)
 5100            {
 05101                if (!hasGraphicalSubs)
 5102                {
 05103                    mainFilters.Add("hwupload_vaapi");
 5104                }
 5105            }
 5106
 5107            /* Make sub and overlay filters for subtitle stream */
 05108            var subFilters = new List<string>();
 05109            var overlayFilters = new List<string>();
 05110            if (isVaInVaOut)
 5111            {
 05112                if (hasSubs)
 5113                {
 05114                    if (hasGraphicalSubs)
 5115                    {
 5116                        // overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead
 05117                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 05118                        subFilters.Add(subPreProcFilters);
 05119                        subFilters.Add("format=bgra");
 5120                    }
 05121                    else if (hasTextSubs)
 5122                    {
 05123                        var framerate = state.VideoStream?.RealFrameRate;
 05124                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5125
 05126                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 05127                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05128                        subFilters.Add(alphaSrcFilter);
 05129                        subFilters.Add("format=bgra");
 05130                        subFilters.Add(subTextSubtitlesFilter);
 5131                    }
 5132
 05133                    subFilters.Add("hwupload=derive_device=vaapi");
 5134
 05135                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 05136                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 05137                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 05138                        : string.Empty;
 05139                    var overlayVaapiFilter = string.Format(
 05140                        CultureInfo.InvariantCulture,
 05141                        "overlay_vaapi=eof_action=pass:repeatlast=0{0}",
 05142                        overlaySize);
 05143                    overlayFilters.Add(overlayVaapiFilter);
 5144                }
 5145            }
 05146            else if (memoryOutput)
 5147            {
 05148                if (hasGraphicalSubs)
 5149                {
 05150                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05151                    subFilters.Add(subPreProcFilters);
 05152                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5153
 05154                    if (isVaapiEncoder)
 5155                    {
 05156                        overlayFilters.Add("hwupload_vaapi");
 5157                    }
 5158                }
 5159            }
 5160
 05161            return (mainFilters, subFilters, overlayFilters);
 5162        }
 5163
 5164        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVaapiFullVidFilter
 5165            EncodingJobInfo state,
 5166            EncodingOptions options,
 5167            string vidDecoder,
 5168            string vidEncoder)
 5169        {
 05170            var inW = state.VideoStream?.Width;
 05171            var inH = state.VideoStream?.Height;
 05172            var reqW = state.BaseRequest.Width;
 05173            var reqH = state.BaseRequest.Height;
 05174            var reqMaxW = state.BaseRequest.MaxWidth;
 05175            var reqMaxH = state.BaseRequest.MaxHeight;
 05176            var threeDFormat = state.MediaSource.Video3DFormat;
 5177
 05178            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05179            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05180            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05181            var isSwEncoder = !isVaapiEncoder;
 05182            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5183
 05184            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05185            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05186            var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
 05187            var doDeintH2645 = doDeintH264 || doDeintHevc;
 5188
 05189            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05190            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05191            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05192            var hasAssSubs = hasSubs
 05193                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05194                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5195
 05196            var rotation = state.VideoStream?.Rotation ?? 0;
 05197            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05198            var doVkTranspose = isVaapiDecoder && !string.IsNullOrEmpty(transposeDir);
 05199            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVkTranspose));
 05200            var swpInW = swapWAndH ? inH : inW;
 05201            var swpInH = swapWAndH ? inW : inH;
 5202
 5203            /* Make main filters for video stream */
 05204            var mainFilters = new List<string>();
 5205
 05206            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doVkTonemap));
 5207
 05208            if (isSwDecoder)
 5209            {
 5210                // INPUT sw surface(memory)
 5211                // sw deint
 05212                if (doDeintH2645)
 5213                {
 05214                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05215                    mainFilters.Add(swDeintFilter);
 5216                }
 5217
 05218                if (doVkTonemap || hasSubs)
 5219                {
 5220                    // sw => hw
 05221                    mainFilters.Add("hwupload=derive_device=vulkan");
 05222                    mainFilters.Add("format=vulkan");
 5223                }
 5224                else
 5225                {
 5226                    // sw scale
 05227                    var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW,
 05228                    mainFilters.Add(swScaleFilter);
 05229                    mainFilters.Add("format=nv12");
 5230                }
 5231            }
 05232            else if (isVaapiDecoder)
 5233            {
 5234                // INPUT vaapi surface(vram)
 05235                if (doVkTranspose || doVkTonemap || hasSubs)
 5236                {
 5237                    // map from vaapi to vulkan/drm via interop (Polaris/gfx8+).
 05238                    if (_mediaEncoder.EncoderVersion >= _minFFmpegAlteredVaVkInterop)
 5239                    {
 05240                        if (doVkTranspose || !_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5241                        {
 5242                            // disable the indirect va-drm-vk mapping since it's no longer reliable.
 05243                            mainFilters.Add("hwmap=derive_device=drm");
 05244                            mainFilters.Add("format=drm_prime");
 05245                            mainFilters.Add("hwmap=derive_device=vulkan");
 05246                            mainFilters.Add("format=vulkan");
 5247
 5248                            // workaround for libplacebo using the imported vulkan frame on gfx8.
 05249                            if (!_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5250                            {
 05251                                mainFilters.Add("scale_vulkan");
 5252                            }
 5253                        }
 05254                        else if (doVkTonemap || hasSubs)
 5255                        {
 5256                            // non ad-hoc libplacebo also accepts drm_prime direct input.
 05257                            mainFilters.Add("hwmap=derive_device=drm");
 05258                            mainFilters.Add("format=drm_prime");
 5259                        }
 5260                    }
 5261                    else // legacy va-vk mapping that works only in jellyfin-ffmpeg6
 5262                    {
 05263                        mainFilters.Add("hwmap=derive_device=vulkan");
 05264                        mainFilters.Add("format=vulkan");
 5265                    }
 5266                }
 5267                else
 5268                {
 5269                    // hw deint
 05270                    if (doDeintH2645)
 5271                    {
 05272                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05273                        mainFilters.Add(deintFilter);
 5274                    }
 5275
 5276                    // hw scale
 05277                    var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", "nv12", false, inW, inH, reqW, reqH, reqMaxW,
 5278
 05279                    if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder && !doVkTonemap)
 5280                    {
 05281                        hwScaleFilter += ":out_range=pc:mode=hq";
 5282                    }
 5283
 05284                    mainFilters.Add(hwScaleFilter);
 5285                }
 5286            }
 5287
 5288            // vk transpose
 05289            if (doVkTranspose)
 5290            {
 05291                if (string.Equals(transposeDir, "reversal", StringComparison.OrdinalIgnoreCase))
 5292                {
 05293                    mainFilters.Add("flip_vulkan");
 5294                }
 5295                else
 5296                {
 05297                    mainFilters.Add($"transpose_vulkan=dir={transposeDir}");
 5298                }
 5299            }
 5300
 5301            // vk libplacebo
 05302            if (doVkTonemap || hasSubs)
 5303            {
 05304                var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, req
 05305                mainFilters.Add(libplaceboFilter);
 05306                mainFilters.Add("format=vulkan");
 5307            }
 5308
 05309            if (doVkTonemap && !hasSubs)
 5310            {
 5311                // OUTPUT vaapi(nv12) surface(vram)
 5312                // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05313                mainFilters.Add("hwmap=derive_device=vaapi");
 05314                mainFilters.Add("format=vaapi");
 5315
 5316                // clear the surf->meta_offset and output nv12
 05317                mainFilters.Add("scale_vaapi=format=nv12");
 5318
 5319                // hw deint
 05320                if (doDeintH2645)
 5321                {
 05322                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05323                    mainFilters.Add(deintFilter);
 5324                }
 5325            }
 5326
 05327            if (!hasSubs)
 5328            {
 5329                // OUTPUT nv12 surface(memory)
 05330                if (isSwEncoder && (doVkTonemap || isVaapiDecoder))
 5331                {
 05332                    mainFilters.Add("hwdownload");
 05333                    mainFilters.Add("format=nv12");
 5334                }
 5335
 05336                if (isSwDecoder && isVaapiEncoder && !doVkTonemap)
 5337                {
 05338                    mainFilters.Add("hwupload_vaapi");
 5339                }
 5340            }
 5341
 5342            /* Make sub and overlay filters for subtitle stream */
 05343            var subFilters = new List<string>();
 05344            var overlayFilters = new List<string>();
 05345            if (hasSubs)
 5346            {
 05347                if (hasGraphicalSubs)
 5348                {
 05349                    var subW = state.SubtitleStream?.Width;
 05350                    var subH = state.SubtitleStream?.Height;
 05351                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05352                    subFilters.Add(subPreProcFilters);
 05353                    subFilters.Add("format=bgra");
 5354                }
 05355                else if (hasTextSubs)
 5356                {
 05357                    var framerate = state.VideoStream?.RealFrameRate;
 05358                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5359
 05360                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05361                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05362                    subFilters.Add(alphaSrcFilter);
 05363                    subFilters.Add("format=bgra");
 05364                    subFilters.Add(subTextSubtitlesFilter);
 5365                }
 5366
 05367                subFilters.Add("hwupload=derive_device=vulkan");
 05368                subFilters.Add("format=vulkan");
 5369
 05370                overlayFilters.Add("overlay_vulkan=eof_action=pass:repeatlast=0");
 5371
 05372                if (isSwEncoder)
 5373                {
 5374                    // OUTPUT nv12 surface(memory)
 05375                    overlayFilters.Add("scale_vulkan=format=nv12");
 05376                    overlayFilters.Add("hwdownload");
 05377                    overlayFilters.Add("format=nv12");
 5378                }
 05379                else if (isVaapiEncoder)
 5380                {
 5381                    // OUTPUT vaapi(nv12) surface(vram)
 5382                    // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05383                    overlayFilters.Add("hwmap=derive_device=vaapi");
 05384                    overlayFilters.Add("format=vaapi");
 5385
 5386                    // clear the surf->meta_offset and output nv12
 05387                    overlayFilters.Add("scale_vaapi=format=nv12");
 5388
 5389                    // hw deint
 05390                    if (doDeintH2645)
 5391                    {
 05392                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05393                        overlayFilters.Add(deintFilter);
 5394                    }
 5395                }
 5396            }
 5397
 05398            return (mainFilters, subFilters, overlayFilters);
 5399        }
 5400
 5401        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiLimitedVidFilter
 5402            EncodingJobInfo state,
 5403            EncodingOptions options,
 5404            string vidDecoder,
 5405            string vidEncoder)
 5406        {
 05407            var inW = state.VideoStream?.Width;
 05408            var inH = state.VideoStream?.Height;
 05409            var reqW = state.BaseRequest.Width;
 05410            var reqH = state.BaseRequest.Height;
 05411            var reqMaxW = state.BaseRequest.MaxWidth;
 05412            var reqMaxH = state.BaseRequest.MaxHeight;
 05413            var threeDFormat = state.MediaSource.Video3DFormat;
 5414
 05415            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05416            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05417            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05418            var isSwEncoder = !isVaapiEncoder;
 05419            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05420            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 05421            var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965;
 05422            var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd;
 5423
 05424            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05425            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05426            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05427            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5428
 05429            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05430            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05431            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 5432
 05433            var rotation = state.VideoStream?.Rotation ?? 0;
 05434            var swapWAndH = Math.Abs(rotation) == 90 && isSwDecoder;
 05435            var swpInW = swapWAndH ? inH : inW;
 05436            var swpInH = swapWAndH ? inW : inH;
 5437
 5438            /* Make main filters for video stream */
 05439            var mainFilters = new List<string>();
 5440
 05441            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5442
 05443            var outFormat = string.Empty;
 05444            if (isSwDecoder)
 5445            {
 5446                // INPUT sw surface(memory)
 5447                // sw deint
 05448                if (doDeintH2645)
 5449                {
 05450                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05451                    mainFilters.Add(swDeintFilter);
 5452                }
 5453
 05454                outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05455                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05456                if (isMjpegEncoder && !doOclTonemap)
 5457                {
 5458                    // sw decoder + hw mjpeg encoder
 05459                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5460                }
 5461
 5462                // sw scale
 05463                mainFilters.Add(swScaleFilter);
 05464                mainFilters.Add("format=" + outFormat);
 5465
 5466                // keep video at memory except ocl tonemap,
 5467                // since the overhead caused by hwupload >>> using sw filter.
 5468                // sw => hw
 05469                if (doOclTonemap)
 5470                {
 05471                    mainFilters.Add("hwupload=derive_device=opencl");
 5472                }
 5473            }
 05474            else if (isVaapiDecoder)
 5475            {
 5476                // INPUT vaapi surface(vram)
 5477                // hw deint
 05478                if (doDeintH2645)
 5479                {
 05480                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05481                    mainFilters.Add(deintFilter);
 5482                }
 5483
 05484                outFormat = doOclTonemap ? string.Empty : "nv12";
 05485                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, inW, inH, reqW, reqH, reqMaxW, 
 5486
 05487                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5488                {
 05489                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05490                    hwScaleFilter += ":mode=hq";
 5491                }
 5492
 5493                // allocate extra pool sizes for vaapi vpp
 05494                if (!string.IsNullOrEmpty(hwScaleFilter))
 5495                {
 05496                    hwScaleFilter += ":extra_hw_frames=24";
 5497                }
 5498
 5499                // hw scale
 05500                mainFilters.Add(hwScaleFilter);
 5501            }
 5502
 05503            if (doOclTonemap && isVaapiDecoder)
 5504            {
 05505                if (isi965Driver)
 5506                {
 5507                    // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05508                    mainFilters.Add("hwmap=derive_device=opencl");
 5509                }
 5510                else
 5511                {
 05512                    mainFilters.Add("hwdownload");
 05513                    mainFilters.Add("format=p010le");
 05514                    mainFilters.Add("hwupload=derive_device=opencl");
 5515                }
 5516            }
 5517
 5518            // ocl tonemap
 05519            if (doOclTonemap)
 5520            {
 05521                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05522                mainFilters.Add(tonemapFilter);
 5523            }
 5524
 05525            if (doOclTonemap && isVaInVaOut)
 5526            {
 05527                if (isi965Driver)
 5528                {
 5529                    // OUTPUT vaapi(nv12) surface(vram)
 5530                    // reverse-mapping via vaapi-opencl interop.
 05531                    mainFilters.Add("hwmap=derive_device=vaapi:reverse=1");
 05532                    mainFilters.Add("format=vaapi");
 5533                }
 5534            }
 5535
 05536            var memoryOutput = false;
 05537            var isUploadForOclTonemap = doOclTonemap && (isSwDecoder || (isVaapiDecoder && !isi965Driver));
 05538            var isHwmapNotUsable = hasGraphicalSubs || isUploadForOclTonemap;
 05539            var isHwmapForSubs = hasSubs && isVaapiDecoder;
 05540            var isHwUnmapForTextSubs = hasTextSubs && isVaInVaOut && !isUploadForOclTonemap;
 05541            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap || isHwmapForSubs)
 5542            {
 05543                memoryOutput = true;
 5544
 5545                // OUTPUT nv12 surface(memory)
 5546                // prefer hwmap to hwdownload on opencl/vaapi.
 05547                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap");
 05548                mainFilters.Add("format=nv12");
 5549            }
 5550
 5551            // OUTPUT nv12 surface(memory)
 05552            if (isSwDecoder && isVaapiEncoder)
 5553            {
 05554                memoryOutput = true;
 5555            }
 5556
 05557            if (memoryOutput)
 5558            {
 5559                // text subtitles
 05560                if (hasTextSubs)
 5561                {
 05562                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05563                    mainFilters.Add(textSubtitlesFilter);
 5564                }
 5565            }
 5566
 05567            if (isHwUnmapForTextSubs)
 5568            {
 05569                mainFilters.Add("hwmap");
 05570                mainFilters.Add("format=vaapi");
 5571            }
 05572            else if (memoryOutput && isVaapiEncoder)
 5573            {
 05574                if (!hasGraphicalSubs)
 5575                {
 05576                    mainFilters.Add("hwupload_vaapi");
 5577                }
 5578            }
 5579
 5580            /* Make sub and overlay filters for subtitle stream */
 05581            var subFilters = new List<string>();
 05582            var overlayFilters = new List<string>();
 05583            if (memoryOutput)
 5584            {
 05585                if (hasGraphicalSubs)
 5586                {
 05587                    var subW = state.SubtitleStream?.Width;
 05588                    var subH = state.SubtitleStream?.Height;
 05589                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05590                    subFilters.Add(subPreProcFilters);
 05591                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5592
 05593                    if (isVaapiEncoder)
 5594                    {
 05595                        overlayFilters.Add("hwupload_vaapi");
 5596                    }
 5597                }
 5598            }
 5599
 05600            return (mainFilters, subFilters, overlayFilters);
 5601        }
 5602
 5603        /// <summary>
 5604        /// Gets the parameter of Apple VideoToolBox filter chain.
 5605        /// </summary>
 5606        /// <param name="state">Encoding state.</param>
 5607        /// <param name="options">Encoding options.</param>
 5608        /// <param name="vidEncoder">Video encoder to use.</param>
 5609        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5610        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFilterChain(
 5611            EncodingJobInfo state,
 5612            EncodingOptions options,
 5613            string vidEncoder)
 5614        {
 05615            if (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 5616            {
 05617                return (null, null, null);
 5618            }
 5619
 5620            // ReSharper disable once InconsistentNaming
 05621            var isMacOS = OperatingSystem.IsMacOS();
 05622            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05623            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05624            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05625            var isVtFullSupported = isMacOS && IsVideoToolboxFullSupported();
 5626
 5627            // legacy videotoolbox pipeline (disable hw filters)
 05628            if (!(isVtEncoder || isVtDecoder)
 05629                || !isVtFullSupported
 05630                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5631            {
 05632                return GetSwVidFilterChain(state, options, vidEncoder);
 5633            }
 5634
 5635            // preferred videotoolbox + metal filters pipeline
 05636            return GetAppleVidFiltersPreferred(state, options, vidDecoder, vidEncoder);
 5637        }
 5638
 5639        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFiltersPrefer
 5640            EncodingJobInfo state,
 5641            EncodingOptions options,
 5642            string vidDecoder,
 5643            string vidEncoder)
 5644        {
 05645            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05646            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05647            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5648
 05649            var inW = state.VideoStream?.Width;
 05650            var inH = state.VideoStream?.Height;
 05651            var reqW = state.BaseRequest.Width;
 05652            var reqH = state.BaseRequest.Height;
 05653            var reqMaxW = state.BaseRequest.MaxWidth;
 05654            var reqMaxH = state.BaseRequest.MaxHeight;
 05655            var threeDFormat = state.MediaSource.Video3DFormat;
 5656
 05657            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05658            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05659            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05660            var doVtTonemap = IsVideoToolboxTonemapAvailable(state, options);
 05661            var doMetalTonemap = !doVtTonemap && IsHwTonemapAvailable(state, options);
 05662            var usingHwSurface = isVtDecoder && (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface);
 5663
 05664            var rotation = state.VideoStream?.Rotation ?? 0;
 05665            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05666            var doVtTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_vt");
 05667            var swapWAndH = Math.Abs(rotation) == 90 && doVtTranspose;
 05668            var swpInW = swapWAndH ? inH : inW;
 05669            var swpInH = swapWAndH ? inW : inH;
 5670
 05671            var scaleFormat = string.Empty;
 5672            // Use P010 for Metal tone mapping, otherwise force an 8bit output.
 05673            if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase))
 5674            {
 05675                if (doMetalTonemap)
 5676                {
 05677                    if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 5678                    {
 05679                        scaleFormat = "p010le";
 5680                    }
 5681                }
 5682                else
 5683                {
 05684                    scaleFormat = "nv12";
 5685                }
 5686            }
 5687
 05688            var hwScaleFilter = GetHwScaleFilter("scale", "vt", scaleFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW,
 5689
 05690            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05691            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05692            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05693            var hasAssSubs = hasSubs
 05694                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05695                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5696
 5697            /* Make main filters for video stream */
 05698            var mainFilters = new List<string>();
 5699
 5700            // hw deint
 05701            if (doDeintH2645)
 5702            {
 05703                var deintFilter = GetHwDeinterlaceFilter(state, options, "videotoolbox");
 05704                mainFilters.Add(deintFilter);
 5705            }
 5706
 5707            // hw transpose
 05708            if (doVtTranspose)
 5709            {
 05710                mainFilters.Add($"transpose_vt=dir={transposeDir}");
 5711            }
 5712
 05713            if (doVtTonemap)
 5714            {
 5715                const string VtTonemapArgs = "color_matrix=bt709:color_primaries=bt709:color_transfer=bt709";
 5716
 5717                // scale_vt can handle scaling & tonemapping in one shot, just like vpp_qsv.
 05718                hwScaleFilter = string.IsNullOrEmpty(hwScaleFilter)
 05719                    ? "scale_vt=" + VtTonemapArgs
 05720                    : hwScaleFilter + ":" + VtTonemapArgs;
 5721            }
 5722
 5723            // hw scale & vt tonemap
 05724            mainFilters.Add(hwScaleFilter);
 5725
 5726            // Metal tonemap
 05727            if (doMetalTonemap)
 5728            {
 05729                var tonemapFilter = GetHwTonemapFilter(options, "videotoolbox", "nv12", isMjpegEncoder);
 05730                mainFilters.Add(tonemapFilter);
 5731            }
 5732
 5733            /* Make sub and overlay filters for subtitle stream */
 05734            var subFilters = new List<string>();
 05735            var overlayFilters = new List<string>();
 5736
 05737            if (hasSubs)
 5738            {
 05739                if (hasGraphicalSubs)
 5740                {
 05741                    var subW = state.SubtitleStream?.Width;
 05742                    var subH = state.SubtitleStream?.Height;
 05743                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05744                    subFilters.Add(subPreProcFilters);
 05745                    subFilters.Add("format=bgra");
 5746                }
 05747                else if (hasTextSubs)
 5748                {
 05749                    var framerate = state.VideoStream?.RealFrameRate;
 05750                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5751
 05752                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05753                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05754                    subFilters.Add(alphaSrcFilter);
 05755                    subFilters.Add("format=bgra");
 05756                    subFilters.Add(subTextSubtitlesFilter);
 5757                }
 5758
 05759                subFilters.Add("hwupload");
 05760                overlayFilters.Add("overlay_videotoolbox=eof_action=pass:repeatlast=0");
 5761            }
 5762
 05763            if (usingHwSurface)
 5764            {
 05765                if (!isVtEncoder)
 5766                {
 05767                    mainFilters.Add("hwdownload");
 05768                    mainFilters.Add("format=nv12");
 5769                }
 5770
 05771                return (mainFilters, subFilters, overlayFilters);
 5772            }
 5773
 5774            // For old jellyfin-ffmpeg that has broken hwsurface, add a hwupload
 05775            var needFiltering = mainFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05776                                subFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05777                                overlayFilters.Any(f => !string.IsNullOrEmpty(f));
 05778            if (needFiltering)
 5779            {
 5780                // INPUT videotoolbox/memory surface(vram/uma)
 5781                // this will pass-through automatically if in/out format matches.
 05782                mainFilters.Insert(0, "hwupload");
 05783                mainFilters.Insert(0, "format=nv12|p010le|videotoolbox_vld");
 5784
 05785                if (!isVtEncoder)
 5786                {
 05787                    mainFilters.Add("hwdownload");
 05788                    mainFilters.Add("format=nv12");
 5789                }
 5790            }
 5791
 05792            return (mainFilters, subFilters, overlayFilters);
 5793        }
 5794
 5795        /// <summary>
 5796        /// Gets the parameter of Rockchip RKMPP/RKRGA filter chain.
 5797        /// </summary>
 5798        /// <param name="state">Encoding state.</param>
 5799        /// <param name="options">Encoding options.</param>
 5800        /// <param name="vidEncoder">Video encoder to use.</param>
 5801        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5802        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFilterChain(
 5803            EncodingJobInfo state,
 5804            EncodingOptions options,
 5805            string vidEncoder)
 5806        {
 05807            if (options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 5808            {
 05809                return (null, null, null);
 5810            }
 5811
 05812            var isLinux = OperatingSystem.IsLinux();
 05813            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05814            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05815            var isSwEncoder = !vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05816            var isRkmppOclSupported = isLinux && IsRkmppFullSupported() && IsOpenclFullSupported();
 5817
 05818            if ((isSwDecoder && isSwEncoder)
 05819                || !isRkmppOclSupported
 05820                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5821            {
 05822                return GetSwVidFilterChain(state, options, vidEncoder);
 5823            }
 5824
 5825            // preferred rkmpp + rkrga + opencl filters pipeline
 05826            if (isRkmppOclSupported)
 5827            {
 05828                return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5829            }
 5830
 05831            return (null, null, null);
 5832        }
 5833
 5834        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFiltersPrefer
 5835            EncodingJobInfo state,
 5836            EncodingOptions options,
 5837            string vidDecoder,
 5838            string vidEncoder)
 5839        {
 05840            var inW = state.VideoStream?.Width;
 05841            var inH = state.VideoStream?.Height;
 05842            var reqW = state.BaseRequest.Width;
 05843            var reqH = state.BaseRequest.Height;
 05844            var reqMaxW = state.BaseRequest.MaxWidth;
 05845            var reqMaxH = state.BaseRequest.MaxHeight;
 05846            var threeDFormat = state.MediaSource.Video3DFormat;
 5847
 05848            var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05849            var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05850            var isSwDecoder = !isRkmppDecoder;
 05851            var isSwEncoder = !isRkmppEncoder;
 05852            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05853            var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder;
 05854            var isEncoderSupportAfbc = isRkmppEncoder
 05855                && (vidEncoder.Contains("h264", StringComparison.OrdinalIgnoreCase)
 05856                    || vidEncoder.Contains("hevc", StringComparison.OrdinalIgnoreCase));
 5857
 05858            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05859            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05860            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05861            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5862
 05863            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05864            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05865            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05866            var hasAssSubs = hasSubs
 05867                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05868                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 05869            var subW = state.SubtitleStream?.Width;
 05870            var subH = state.SubtitleStream?.Height;
 5871
 05872            var rotation = state.VideoStream?.Rotation ?? 0;
 05873            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05874            var doRkVppTranspose = !string.IsNullOrEmpty(transposeDir);
 05875            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isRkmppDecoder && doRkVppTranspose));
 05876            var swpInW = swapWAndH ? inH : inW;
 05877            var swpInH = swapWAndH ? inW : inH;
 5878
 5879            /* Make main filters for video stream */
 05880            var mainFilters = new List<string>();
 5881
 05882            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5883
 05884            if (isSwDecoder)
 5885            {
 5886                // INPUT sw surface(memory)
 5887                // sw deint
 05888                if (doDeintH2645)
 5889                {
 05890                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05891                    mainFilters.Add(swDeintFilter);
 5892                }
 5893
 05894                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 05895                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05896                if (isMjpegEncoder && !doOclTonemap)
 5897                {
 5898                    // sw decoder + hw mjpeg encoder
 05899                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5900                }
 5901
 05902                if (!string.IsNullOrEmpty(swScaleFilter))
 5903                {
 05904                    swScaleFilter += ":flags=fast_bilinear";
 5905                }
 5906
 5907                // sw scale
 05908                mainFilters.Add(swScaleFilter);
 05909                mainFilters.Add($"format={outFormat}");
 5910
 5911                // keep video at memory except ocl tonemap,
 5912                // since the overhead caused by hwupload >>> using sw filter.
 5913                // sw => hw
 05914                if (doOclTonemap)
 5915                {
 05916                    mainFilters.Add("hwupload=derive_device=opencl");
 5917                }
 5918            }
 05919            else if (isRkmppDecoder)
 5920            {
 5921                // INPUT rkmpp/drm surface(gem/dma-heap)
 5922
 05923                var isFullAfbcPipeline = isEncoderSupportAfbc && isDrmInDrmOut && !doOclTonemap;
 05924                var swapOutputWandH = doRkVppTranspose && swapWAndH;
 05925                var outFormat = doOclTonemap ? "p010" : (isMjpegEncoder ? "bgra" : "nv12"); // RGA only support full ran
 05926                var hwScaleFilter = GetHwScaleFilter("vpp", "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, r
 05927                var doScaling = GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, swpInW, swpInH, reqW, re
 5928
 05929                if (!hasSubs
 05930                     || doRkVppTranspose
 05931                     || !isFullAfbcPipeline
 05932                     || !string.IsNullOrEmpty(doScaling))
 5933                {
 5934                    // RGA3 hardware only support (1/8 ~ 8) scaling in each blit operation,
 5935                    // but in Trickplay there's a case: (3840/320 == 12), enable 2pass for it
 05936                    if (!string.IsNullOrEmpty(doScaling)
 05937                        && !IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f))
 5938                    {
 5939                        // Vendor provided BSP kernel has an RGA driver bug that causes the output to be corrupted for P
 5940                        // Use NV15 instead of P010 to avoid the issue.
 5941                        // SDR inputs are using BGRA formats already which is not affected.
 05942                        var intermediateFormat = string.Equals(outFormat, "p010", StringComparison.OrdinalIgnoreCase) ? 
 05943                        var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={intermediateFormat}:force_o
 05944                        mainFilters.Add(hwScaleFilterFirstPass);
 5945                    }
 5946
 05947                    if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose)
 5948                    {
 05949                        hwScaleFilter += $":transpose={transposeDir}";
 5950                    }
 5951
 5952                    // try enabling AFBC to save DDR bandwidth
 05953                    if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline)
 5954                    {
 05955                        hwScaleFilter += ":afbc=1";
 5956                    }
 5957
 5958                    // hw transpose & scale
 05959                    mainFilters.Add(hwScaleFilter);
 5960                }
 5961            }
 5962
 05963            if (doOclTonemap && isRkmppDecoder)
 5964            {
 5965                // map from rkmpp/drm to opencl via drm-opencl interop.
 05966                mainFilters.Add("hwmap=derive_device=opencl");
 5967            }
 5968
 5969            // ocl tonemap
 05970            if (doOclTonemap)
 5971            {
 05972                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05973                mainFilters.Add(tonemapFilter);
 5974            }
 5975
 05976            var memoryOutput = false;
 05977            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 05978            if ((isRkmppDecoder && isSwEncoder) || isUploadForOclTonemap)
 5979            {
 05980                memoryOutput = true;
 5981
 5982                // OUTPUT nv12 surface(memory)
 05983                mainFilters.Add("hwdownload");
 05984                mainFilters.Add("format=nv12");
 5985            }
 5986
 5987            // OUTPUT nv12 surface(memory)
 05988            if (isSwDecoder && isRkmppEncoder)
 5989            {
 05990                memoryOutput = true;
 5991            }
 5992
 05993            if (memoryOutput)
 5994            {
 5995                // text subtitles
 05996                if (hasTextSubs)
 5997                {
 05998                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05999                    mainFilters.Add(textSubtitlesFilter);
 6000                }
 6001            }
 6002
 06003            if (isDrmInDrmOut)
 6004            {
 06005                if (doOclTonemap)
 6006                {
 6007                    // OUTPUT drm(nv12) surface(gem/dma-heap)
 6008                    // reverse-mapping via drm-opencl interop.
 06009                    mainFilters.Add("hwmap=derive_device=rkmpp:reverse=1");
 06010                    mainFilters.Add("format=drm_prime");
 6011                }
 6012            }
 6013
 6014            /* Make sub and overlay filters for subtitle stream */
 06015            var subFilters = new List<string>();
 06016            var overlayFilters = new List<string>();
 06017            if (isDrmInDrmOut)
 6018            {
 06019                if (hasSubs)
 6020                {
 06021                    var subMaxH = 1080;
 06022                    if (hasGraphicalSubs)
 6023                    {
 06024                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 06025                        subFilters.Add(subPreProcFilters);
 06026                        subFilters.Add("format=bgra");
 6027                    }
 06028                    else if (hasTextSubs)
 6029                    {
 06030                        var framerate = state.VideoStream?.RealFrameRate;
 06031                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 6032
 6033                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 06034                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, subMaxH, subF
 06035                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 06036                        subFilters.Add(alphaSrcFilter);
 06037                        subFilters.Add("format=bgra");
 06038                        subFilters.Add(subTextSubtitlesFilter);
 6039                    }
 6040
 06041                    subFilters.Add("hwupload=derive_device=rkmpp");
 6042
 6043                    // offload 1080p+ subtitles swscale upscaling from CPU to RGA
 06044                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 06045                    if (overlayW.HasValue && overlayH.HasValue && overlayH.Value > subMaxH)
 6046                    {
 06047                        subFilters.Add($"vpp_rkrga=w={overlayW.Value}:h={overlayH.Value}:format=bgra:afbc=1");
 6048                    }
 6049
 6050                    // try enabling AFBC to save DDR bandwidth
 06051                    var hwOverlayFilter = "overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12";
 06052                    if (isEncoderSupportAfbc)
 6053                    {
 06054                        hwOverlayFilter += ":afbc=1";
 6055                    }
 6056
 06057                    overlayFilters.Add(hwOverlayFilter);
 6058                }
 6059            }
 06060            else if (memoryOutput)
 6061            {
 06062                if (hasGraphicalSubs)
 6063                {
 06064                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 06065                    subFilters.Add(subPreProcFilters);
 06066                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 6067                }
 6068            }
 6069
 06070            return (mainFilters, subFilters, overlayFilters);
 6071        }
 6072
 6073        /// <summary>
 6074        /// Gets the parameter of video processing filters.
 6075        /// </summary>
 6076        /// <param name="state">Encoding state.</param>
 6077        /// <param name="options">Encoding options.</param>
 6078        /// <param name="outputVideoCodec">Video codec to use.</param>
 6079        /// <returns>The video processing filters parameter.</returns>
 6080        public string GetVideoProcessingFilterParam(
 6081            EncodingJobInfo state,
 6082            EncodingOptions options,
 6083            string outputVideoCodec)
 6084        {
 06085            var videoStream = state.VideoStream;
 06086            if (videoStream is null)
 6087            {
 06088                return string.Empty;
 6089            }
 6090
 06091            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 06092            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 06093            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 6094
 6095            List<string> mainFilters;
 6096            List<string> subFilters;
 6097            List<string> overlayFilters;
 6098
 06099            (mainFilters, subFilters, overlayFilters) = options.HardwareAccelerationType switch
 06100            {
 06101                HardwareAccelerationType.vaapi => GetVaapiVidFilterChain(state, options, outputVideoCodec),
 06102                HardwareAccelerationType.amf => GetAmdVidFilterChain(state, options, outputVideoCodec),
 06103                HardwareAccelerationType.qsv => GetIntelVidFilterChain(state, options, outputVideoCodec),
 06104                HardwareAccelerationType.nvenc => GetNvidiaVidFilterChain(state, options, outputVideoCodec),
 06105                HardwareAccelerationType.videotoolbox => GetAppleVidFilterChain(state, options, outputVideoCodec),
 06106                HardwareAccelerationType.rkmpp => GetRkmppVidFilterChain(state, options, outputVideoCodec),
 06107                _ => GetSwVidFilterChain(state, options, outputVideoCodec),
 06108            };
 6109
 06110            mainFilters?.RemoveAll(string.IsNullOrEmpty);
 06111            subFilters?.RemoveAll(string.IsNullOrEmpty);
 06112            overlayFilters?.RemoveAll(string.IsNullOrEmpty);
 6113
 06114            var framerate = GetFramerateParam(state);
 06115            if (framerate.HasValue)
 6116            {
 06117                mainFilters.Insert(0, string.Format(
 06118                    CultureInfo.InvariantCulture,
 06119                    "fps={0}",
 06120                    framerate.Value));
 6121            }
 6122
 06123            var mainStr = string.Empty;
 06124            if (mainFilters?.Count > 0)
 6125            {
 06126                mainStr = string.Format(
 06127                    CultureInfo.InvariantCulture,
 06128                    "{0}",
 06129                    string.Join(',', mainFilters));
 6130            }
 6131
 06132            if (overlayFilters?.Count == 0)
 6133            {
 6134                // -vf "scale..."
 06135                return string.IsNullOrEmpty(mainStr) ? string.Empty : " -vf \"" + mainStr + "\"";
 6136            }
 6137
 06138            if (overlayFilters?.Count > 0
 06139                && subFilters?.Count > 0
 06140                && state.SubtitleStream is not null)
 6141            {
 6142                // overlay graphical/text subtitles
 06143                var subStr = string.Format(
 06144                        CultureInfo.InvariantCulture,
 06145                        "{0}",
 06146                        string.Join(',', subFilters));
 6147
 06148                var overlayStr = string.Format(
 06149                        CultureInfo.InvariantCulture,
 06150                        "{0}",
 06151                        string.Join(',', overlayFilters));
 6152
 06153                var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
 06154                var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 06155                var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 6156
 06157                if (hasSubs)
 6158                {
 6159                    // -filter_complex "[0:s]scale=s[sub]..."
 06160                    var filterStr = string.IsNullOrEmpty(mainStr)
 06161                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]{5}\""
 06162                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6163
 06164                    if (hasTextSubs)
 6165                    {
 06166                        filterStr = string.IsNullOrEmpty(mainStr)
 06167                            ? " -filter_complex \"{4}[sub];[0:{2}][sub]{5}\""
 06168                            : " -filter_complex \"{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6169                    }
 6170
 06171                    return string.Format(
 06172                        CultureInfo.InvariantCulture,
 06173                        filterStr,
 06174                        mapPrefix,
 06175                        subtitleStreamIndex,
 06176                        videoStreamIndex,
 06177                        mainStr,
 06178                        subStr,
 06179                        overlayStr);
 6180                }
 6181            }
 6182
 06183            return string.Empty;
 6184        }
 6185
 6186        public string GetOverwriteColorPropertiesParam(EncodingJobInfo state, bool isTonemapAvailable)
 6187        {
 06188            if (isTonemapAvailable)
 6189            {
 06190                return GetInputHdrParam(state.VideoStream?.ColorTransfer);
 6191            }
 6192
 06193            return GetOutputSdrParam(null);
 6194        }
 6195
 6196        public string GetInputHdrParam(string colorTransfer)
 6197        {
 06198            if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
 6199            {
 6200                // HLG
 06201                return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
 6202            }
 6203
 6204            // HDR10
 06205            return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc";
 6206        }
 6207
 6208        public string GetOutputSdrParam(string tonemappingRange)
 6209        {
 6210            // SDR
 06211            if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
 6212            {
 06213                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
 6214            }
 6215
 06216            if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
 6217            {
 06218                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc";
 6219            }
 6220
 06221            return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
 6222        }
 6223
 6224        public static int GetVideoColorBitDepth(EncodingJobInfo state)
 6225        {
 06226            var videoStream = state.VideoStream;
 06227            if (videoStream is not null)
 6228            {
 06229                if (videoStream.BitDepth.HasValue)
 6230                {
 06231                    return videoStream.BitDepth.Value;
 6232                }
 6233
 06234                if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
 06235                    || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
 06236                    || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 06237                    || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
 6238                {
 06239                    return 8;
 6240                }
 6241
 06242                if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 06243                    || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 06244                    || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
 6245                {
 06246                    return 10;
 6247                }
 6248
 06249                if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 06250                    || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 06251                    || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
 6252                {
 06253                    return 12;
 6254                }
 6255
 06256                return 8;
 6257            }
 6258
 06259            return 0;
 6260        }
 6261
 6262        /// <summary>
 6263        /// Gets the ffmpeg option string for the hardware accelerated video decoder.
 6264        /// </summary>
 6265        /// <param name="state">The encoding job info.</param>
 6266        /// <param name="options">The encoding options.</param>
 6267        /// <returns>The option string or null if none available.</returns>
 6268        protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
 6269        {
 06270            var videoStream = state.VideoStream;
 06271            var mediaSource = state.MediaSource;
 06272            if (videoStream is null || mediaSource is null)
 6273            {
 06274                return null;
 6275            }
 6276
 6277            // HWA decoders can handle both video files and video folders.
 06278            var videoType = state.VideoType;
 06279            if (videoType != VideoType.VideoFile
 06280                && videoType != VideoType.Iso
 06281                && videoType != VideoType.Dvd
 06282                && videoType != VideoType.BluRay)
 6283            {
 06284                return null;
 6285            }
 6286
 06287            if (IsCopyCodec(state.OutputVideoCodec))
 6288            {
 06289                return null;
 6290            }
 6291
 06292            var hardwareAccelerationType = options.HardwareAccelerationType;
 6293
 06294            if (!string.IsNullOrEmpty(videoStream.Codec) && hardwareAccelerationType != HardwareAccelerationType.none)
 6295            {
 06296                var bitDepth = GetVideoColorBitDepth(state);
 6297
 6298                // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support for most platforms
 06299                if (bitDepth == 10
 06300                    && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06301                         || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
 06302                         || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)
 06303                         || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)))
 6304                {
 6305                    // RKMPP has H.264 Hi10P decoder
 06306                    bool hasHardwareHi10P = hardwareAccelerationType == HardwareAccelerationType.rkmpp;
 6307
 6308                    // VideoToolbox on Apple Silicon has H.264 Hi10P mode enabled after macOS 14.6
 06309                    if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox)
 6310                    {
 06311                        var ver = Environment.OSVersion.Version;
 06312                        var arch = RuntimeInformation.OSArchitecture;
 06313                        if (arch.Equals(Architecture.Arm64) && ver >= new Version(14, 6))
 6314                        {
 06315                            hasHardwareHi10P = true;
 6316                        }
 6317                    }
 6318
 06319                    if (!hasHardwareHi10P
 06320                        && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6321                    {
 06322                        return null;
 6323                    }
 6324                }
 6325
 06326                var decoder = hardwareAccelerationType switch
 06327                {
 06328                    HardwareAccelerationType.vaapi => GetVaapiVidDecoder(state, options, videoStream, bitDepth),
 06329                    HardwareAccelerationType.amf => GetAmfVidDecoder(state, options, videoStream, bitDepth),
 06330                    HardwareAccelerationType.qsv => GetQsvHwVidDecoder(state, options, videoStream, bitDepth),
 06331                    HardwareAccelerationType.nvenc => GetNvdecVidDecoder(state, options, videoStream, bitDepth),
 06332                    HardwareAccelerationType.videotoolbox => GetVideotoolboxVidDecoder(state, options, videoStream, bitD
 06333                    HardwareAccelerationType.rkmpp => GetRkmppVidDecoder(state, options, videoStream, bitDepth),
 06334                    _ => string.Empty
 06335                };
 6336
 06337                if (!string.IsNullOrEmpty(decoder))
 6338                {
 06339                    return decoder;
 6340                }
 6341            }
 6342
 6343            // leave blank so ffmpeg will decide
 06344            return null;
 6345        }
 6346
 6347        /// <summary>
 6348        /// Gets a hw decoder name.
 6349        /// </summary>
 6350        /// <param name="options">Encoding options.</param>
 6351        /// <param name="decoderPrefix">Decoder prefix.</param>
 6352        /// <param name="decoderSuffix">Decoder suffix.</param>
 6353        /// <param name="videoCodec">Video codec to use.</param>
 6354        /// <param name="bitDepth">Video color bit depth.</param>
 6355        /// <returns>Hardware decoder name.</returns>
 6356        public string GetHwDecoderName(EncodingOptions options, string decoderPrefix, string decoderSuffix, string video
 6357        {
 06358            if (string.IsNullOrEmpty(decoderPrefix) || string.IsNullOrEmpty(decoderSuffix))
 6359            {
 06360                return null;
 6361            }
 6362
 06363            var decoderName = decoderPrefix + '_' + decoderSuffix;
 6364
 06365            var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains
 6366
 6367            // VideoToolbox decoders have built-in SW fallback
 06368            if (bitDepth == 10
 06369                && isCodecAvailable
 06370                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6371            {
 06372                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06373                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
 06374                    && !options.EnableDecodingColorDepth10Hevc)
 6375                {
 06376                    return null;
 6377                }
 6378
 06379                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06380                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06381                    && !options.EnableDecodingColorDepth10Vp9)
 6382                {
 06383                    return null;
 6384                }
 6385            }
 6386
 06387            if (string.Equals(decoderSuffix, "cuvid", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdec
 6388            {
 06389                return null;
 6390            }
 6391
 06392            if (string.Equals(decoderSuffix, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwD
 6393            {
 06394                return null;
 6395            }
 6396
 06397            if (string.Equals(decoderSuffix, "rkmpp", StringComparison.OrdinalIgnoreCase))
 6398            {
 06399                return null;
 6400            }
 6401
 06402            return isCodecAvailable ? (" -c:v " + decoderName) : null;
 6403        }
 6404
 6405        /// <summary>
 6406        /// Gets a hwaccel type to use as a hardware decoder depending on the system.
 6407        /// </summary>
 6408        /// <param name="state">Encoding state.</param>
 6409        /// <param name="options">Encoding options.</param>
 6410        /// <param name="videoCodec">Video codec to use.</param>
 6411        /// <param name="bitDepth">Video color bit depth.</param>
 6412        /// <param name="outputHwSurface">Specifies if output hw surface.</param>
 6413        /// <returns>Hardware accelerator type.</returns>
 6414        public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, int bitDepth, bo
 6415        {
 06416            var isWindows = OperatingSystem.IsWindows();
 06417            var isLinux = OperatingSystem.IsLinux();
 06418            var isMacOS = OperatingSystem.IsMacOS();
 06419            var isD3d11Supported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va");
 06420            var isVaapiSupported = isLinux && IsVaapiSupported(state);
 06421            var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported();
 06422            var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv");
 06423            var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
 06424            var isRkmppSupported = isLinux && IsRkmppFullSupported();
 06425            var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCas
 06426            var hardwareAccelerationType = options.HardwareAccelerationType;
 6427
 06428            var ffmpegVersion = _mediaEncoder.EncoderVersion;
 6429
 6430            // Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
 06431            var isAv1 = ffmpegVersion < _minFFmpegImplicitHwaccel
 06432                && string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
 6433
 6434            // Allow profile mismatch if decoding H.264 baseline with d3d11va and vaapi hwaccels.
 06435            var profileMismatch = string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
 06436                && string.Equals(state.VideoStream?.Profile, "baseline", StringComparison.OrdinalIgnoreCase);
 6437
 6438            // Disable the extra internal copy in nvdec. We already handle it in filter chain.
 06439            var nvdecNoInternalCopy = ffmpegVersion >= _minFFmpegHwaUnsafeOutput;
 6440
 6441            // Strip the display rotation side data from the transposed fmp4 output stream.
 06442            var stripRotationData = (state.VideoStream?.Rotation ?? 0) != 0
 06443                && ffmpegVersion >= _minFFmpegDisplayRotationOption;
 06444            var stripRotationDataArgs = stripRotationData ? " -display_rotation 0" : string.Empty;
 6445
 6446            // VideoToolbox decoders have built-in SW fallback
 06447            if (isCodecAvailable
 06448                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6449            {
 06450                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06451                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase))
 6452                {
 06453                    if (IsVideoStreamHevcRext(state))
 6454                    {
 06455                        if (bitDepth <= 10 && !options.EnableDecodingColorDepth10HevcRext)
 6456                        {
 06457                            return null;
 6458                        }
 6459
 06460                        if (bitDepth == 12 && !options.EnableDecodingColorDepth12HevcRext)
 6461                        {
 06462                            return null;
 6463                        }
 6464
 06465                        if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06466                            && !_mediaEncoder.IsVaapiDeviceInteliHD)
 6467                        {
 06468                            return null;
 6469                        }
 6470                    }
 06471                    else if (bitDepth == 10 && !options.EnableDecodingColorDepth10Hevc)
 6472                    {
 06473                        return null;
 6474                    }
 6475                }
 6476
 06477                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06478                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06479                    && bitDepth == 10
 06480                    && !options.EnableDecodingColorDepth10Vp9)
 6481                {
 06482                    return null;
 6483                }
 6484            }
 6485
 6486            // Intel qsv/d3d11va/vaapi
 06487            if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 6488            {
 06489                if (options.PreferSystemNativeHwDecoder)
 6490                {
 06491                    if (isVaapiSupported && isCodecAvailable)
 6492                    {
 06493                        return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + st
 06494                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " 
 6495                    }
 6496
 06497                    if (isD3d11Supported && isCodecAvailable)
 6498                    {
 06499                        return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + 
 06500                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 
 6501                    }
 6502                }
 6503                else
 6504                {
 06505                    if (isQsvSupported && isCodecAvailable)
 6506                    {
 06507                        return " -hwaccel qsv" + (outputHwSurface ? " -hwaccel_output_format qsv -noautorotate" + stripR
 6508                    }
 6509                }
 6510            }
 6511
 6512            // Nvidia cuda
 06513            if (hardwareAccelerationType == HardwareAccelerationType.nvenc)
 6514            {
 06515                if (isCudaSupported && isCodecAvailable)
 6516                {
 06517                    if (options.EnableEnhancedNvdecDecoder)
 6518                    {
 6519                        // set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support.
 06520                        return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stri
 06521                            + (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + 
 6522                    }
 6523
 6524                    // cuvid decoder doesn't have threading issue.
 06525                    return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stripRot
 6526                }
 6527            }
 6528
 6529            // Amd d3d11va
 06530            if (hardwareAccelerationType == HardwareAccelerationType.amf)
 6531            {
 06532                if (isD3d11Supported && isCodecAvailable)
 6533                {
 06534                    return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + stri
 06535                        + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v
 6536                }
 6537            }
 6538
 6539            // Vaapi
 06540            if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06541                && isVaapiSupported
 06542                && isCodecAvailable)
 6543            {
 06544                return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + stripRotat
 06545                    + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1
 6546            }
 6547
 6548            // Apple videotoolbox
 06549            if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06550                && isVideotoolboxSupported
 06551                && isCodecAvailable)
 6552            {
 06553                return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string
 6554            }
 6555
 6556            // Rockchip rkmpp
 06557            if (hardwareAccelerationType == HardwareAccelerationType.rkmpp
 06558                && isRkmppSupported
 06559                && isCodecAvailable)
 6560            {
 06561                return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime -noautorotate" + stripR
 6562            }
 6563
 06564            return null;
 6565        }
 6566
 6567        public string GetQsvHwVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6568        {
 06569            var isWindows = OperatingSystem.IsWindows();
 06570            var isLinux = OperatingSystem.IsLinux();
 6571
 06572            if ((!isWindows && !isLinux)
 06573                || options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 6574            {
 06575                return null;
 6576            }
 6577
 06578            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 06579            var isIntelDx11OclSupported = isWindows
 06580                && _mediaEncoder.SupportsHwaccel("d3d11va")
 06581                && isQsvOclSupported;
 06582            var isIntelVaapiOclSupported = isLinux
 06583                && IsVaapiSupported(state)
 06584                && isQsvOclSupported;
 06585            var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
 06586                && _mediaEncoder.SupportsFilter("alphasrc");
 6587
 06588            var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06589                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06590            var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 06591            var is8_10_12bitSwFormatsQsv = is8_10bitSwFormatsQsv
 06592                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06593                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06594                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06595                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06596                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06597                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06598                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6599            // TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
 6600
 06601            if (is8bitSwFormatsQsv)
 6602            {
 06603                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06604                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6605                {
 06606                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6607                }
 6608
 06609                if (string.Equals(videoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase))
 6610                {
 06611                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6612                }
 6613
 06614                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6615                {
 06616                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6617                }
 6618
 06619                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6620                {
 06621                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6622                }
 6623            }
 6624
 06625            if (is8_10bitSwFormatsQsv)
 6626            {
 06627                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6628                {
 06629                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6630                }
 6631
 06632                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 6633                {
 06634                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6635                }
 6636            }
 6637
 06638            if (is8_10_12bitSwFormatsQsv)
 6639            {
 06640                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06641                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6642                {
 06643                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6644                }
 6645            }
 6646
 06647            return null;
 6648        }
 6649
 6650        public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6651        {
 06652            if ((!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
 06653                || options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 6654            {
 06655                return null;
 6656            }
 6657
 06658            var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
 06659            var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06660                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06661            var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06662            var is8_10_12bitSwFormatsNvdec = is8_10bitSwFormatsNvdec
 06663                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06664                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06665                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06666                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6667            // TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
 6668
 06669            if (is8bitSwFormatsNvdec)
 6670            {
 06671                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06672                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6673                {
 06674                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6675                }
 6676
 06677                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6678                {
 06679                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6680                }
 6681
 06682                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6683                {
 06684                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6685                }
 6686
 06687                if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6688                {
 06689                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpe
 6690                }
 6691
 06692                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6693                {
 06694                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6695                }
 6696            }
 6697
 06698            if (is8_10bitSwFormatsNvdec)
 6699            {
 06700                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6701                {
 06702                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6703                }
 6704
 06705                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6706                {
 06707                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6708                }
 6709            }
 6710
 06711            if (is8_10_12bitSwFormatsNvdec)
 6712            {
 06713                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06714                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6715                {
 06716                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6717                }
 6718            }
 6719
 06720            return null;
 6721        }
 6722
 6723        public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitD
 6724        {
 06725            if (!OperatingSystem.IsWindows()
 06726                || options.HardwareAccelerationType != HardwareAccelerationType.amf)
 6727            {
 06728                return null;
 6729            }
 6730
 06731            var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
 06732                && IsOpenclFullSupported()
 06733                && _mediaEncoder.SupportsFilter("alphasrc");
 06734            var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06735                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06736            var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 6737
 06738            if (is8bitSwFormatsAmf)
 6739            {
 06740                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06741                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6742                {
 06743                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6744                }
 6745
 06746                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6747                {
 06748                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6749                }
 6750
 06751                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6752                {
 06753                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6754                }
 6755            }
 6756
 06757            if (is8_10bitSwFormatsAmf)
 6758            {
 06759                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06760                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6761                {
 06762                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6763                }
 6764
 06765                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6766                {
 06767                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6768                }
 6769
 06770                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6771                {
 06772                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6773                }
 6774            }
 6775
 06776            return null;
 6777        }
 6778
 6779        public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6780        {
 06781            if (!OperatingSystem.IsLinux()
 06782                || options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 6783            {
 06784                return null;
 6785            }
 6786
 06787            var hwSurface = IsVaapiSupported(state)
 06788                && IsVaapiFullSupported()
 06789                && IsOpenclFullSupported()
 06790                && _mediaEncoder.SupportsFilter("alphasrc");
 06791            var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06792                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06793            var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06794            var is8_10_12bitSwFormatsVaapi = is8_10bitSwFormatsVaapi
 06795                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06796                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06797                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06798                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06799                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06800                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06801                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6802
 06803            if (is8bitSwFormatsVaapi)
 6804            {
 06805                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06806                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6807                {
 06808                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6809                }
 6810
 06811                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6812                {
 06813                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6814                }
 6815
 06816                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6817                {
 06818                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6819                }
 6820
 06821                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6822                {
 06823                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6824                }
 6825            }
 6826
 06827            if (is8_10bitSwFormatsVaapi)
 6828            {
 06829                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6830                {
 06831                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6832                }
 6833
 06834                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6835                {
 06836                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6837                }
 6838            }
 6839
 06840            if (is8_10_12bitSwFormatsVaapi)
 6841            {
 06842                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06843                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6844                {
 06845                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6846                }
 6847            }
 6848
 06849            return null;
 6850        }
 6851
 6852        public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream,
 6853        {
 06854            if (!OperatingSystem.IsMacOS()
 06855                || options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 6856            {
 06857                return null;
 6858            }
 6859
 06860            var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase
 06861                                    || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnore
 06862            var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, String
 06863            var is8_10_12bitSwFormatsVt = is8_10bitSwFormatsVt
 06864                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06865                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06866                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06867                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06868                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06869                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06870                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 06871            var isAv1SupportedSwFormatsVt = is8_10bitSwFormatsVt || string.Equals("yuv420p12le", videoStream.PixelFormat
 6872
 6873            // The related patches make videotoolbox hardware surface working is only available in jellyfin-ffmpeg 7.0.1
 06874            bool useHwSurface = (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface) && IsVideoToolboxFullSupp
 6875
 06876            if (is8bitSwFormatsVt)
 6877            {
 06878                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6879                {
 06880                    return GetHwaccelType(state, options, "vp8", bitDepth, useHwSurface);
 6881                }
 6882            }
 6883
 06884            if (is8_10bitSwFormatsVt)
 6885            {
 06886                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06887                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6888                {
 06889                    return GetHwaccelType(state, options, "h264", bitDepth, useHwSurface);
 6890                }
 6891
 06892                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6893                {
 06894                    return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
 6895                }
 6896            }
 6897
 06898            if (is8_10_12bitSwFormatsVt)
 6899            {
 06900                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06901                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6902                {
 06903                    return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
 6904                }
 6905
 06906                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06907                    && isAv1SupportedSwFormatsVt
 06908                    && _mediaEncoder.IsVideoToolboxAv1DecodeAvailable)
 6909                {
 06910                    return GetHwaccelType(state, options, "av1", bitDepth, useHwSurface);
 6911                }
 6912            }
 6913
 06914            return null;
 6915        }
 6916
 6917        public string GetRkmppVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6918        {
 06919            var isLinux = OperatingSystem.IsLinux();
 6920
 06921            if (!isLinux
 06922                || options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 6923            {
 06924                return null;
 6925            }
 6926
 06927            var inW = state.VideoStream?.Width;
 06928            var inH = state.VideoStream?.Height;
 06929            var reqW = state.BaseRequest.Width;
 06930            var reqH = state.BaseRequest.Height;
 06931            var reqMaxW = state.BaseRequest.MaxWidth;
 06932            var reqMaxH = state.BaseRequest.MaxHeight;
 6933
 6934            // rkrga RGA2e supports range from 1/16 to 16
 06935            if (!IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 16.0f))
 6936            {
 06937                return null;
 6938            }
 6939
 06940            var isRkmppOclSupported = IsRkmppFullSupported() && IsOpenclFullSupported();
 06941            var hwSurface = isRkmppOclSupported
 06942                && _mediaEncoder.SupportsFilter("alphasrc");
 6943
 6944            // rkrga RGA3 supports range from 1/8 to 8
 06945            var isAfbcSupported = hwSurface && IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 6946
 6947            // TODO: add more 8/10bit and 4:2:2 formats for Rkmpp after finishing the ffcheck tool
 06948            var is8bitSwFormatsRkmpp = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06949                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06950            var is10bitSwFormatsRkmpp = string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIg
 06951            var is8_10bitSwFormatsRkmpp = is8bitSwFormatsRkmpp || is10bitSwFormatsRkmpp;
 6952
 6953            // nv15 and nv20 are bit-stream only formats
 06954            if (is10bitSwFormatsRkmpp && !hwSurface)
 6955            {
 06956                return null;
 6957            }
 6958
 06959            if (is8bitSwFormatsRkmpp)
 6960            {
 06961                if (string.Equals(videoStream.Codec, "mpeg1video", StringComparison.OrdinalIgnoreCase))
 6962                {
 06963                    return GetHwaccelType(state, options, "mpeg1video", bitDepth, hwSurface);
 6964                }
 6965
 06966                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6967                {
 06968                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6969                }
 6970
 06971                if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 6972                {
 06973                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
 6974                }
 6975
 06976                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6977                {
 06978                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6979                }
 6980            }
 6981
 06982            if (is8_10bitSwFormatsRkmpp)
 6983            {
 06984                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06985                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6986                {
 06987                    var accelType = GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 06988                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 6989                }
 6990
 06991                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06992                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6993                {
 06994                    var accelType = GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 06995                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 6996                }
 6997
 06998                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6999                {
 07000                    var accelType = GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 07001                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7002                }
 7003
 07004                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 7005                {
 07006                    var accelType = GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 07007                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7008                }
 7009            }
 7010
 07011            return null;
 7012        }
 7013
 7014        /// <summary>
 7015        /// Gets the number of threads.
 7016        /// </summary>
 7017        /// <param name="state">Encoding state.</param>
 7018        /// <param name="encodingOptions">Encoding options.</param>
 7019        /// <param name="outputVideoCodec">Video codec to use.</param>
 7020        /// <returns>Number of threads.</returns>
 7021#nullable enable
 7022        public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVide
 7023        {
 07024            var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount;
 7025
 07026            if (threads <= 0)
 7027            {
 7028                // Automatically set thread count
 07029                return 0;
 7030            }
 7031
 07032            return Math.Min(threads, Environment.ProcessorCount);
 7033        }
 7034
 7035#nullable disable
 7036        public void TryStreamCopy(EncodingJobInfo state)
 7037        {
 07038            if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream))
 7039            {
 07040                state.OutputVideoCodec = "copy";
 7041            }
 7042            else
 7043            {
 07044                var user = state.User;
 7045
 7046                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07047                if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
 7048                {
 07049                    state.OutputVideoCodec = "copy";
 7050                }
 7051            }
 7052
 07053            if (state.AudioStream is not null
 07054                && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs))
 7055            {
 07056                state.OutputAudioCodec = "copy";
 7057            }
 7058            else
 7059            {
 07060                var user = state.User;
 7061
 7062                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07063                if (user is not null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
 7064                {
 07065                    state.OutputAudioCodec = "copy";
 7066                }
 7067            }
 07068        }
 7069
 7070        public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
 7071        {
 07072            var inputModifier = string.Empty;
 07073            var analyzeDurationArgument = string.Empty;
 7074
 7075            // Apply -analyzeduration as per the environment variable,
 7076            // otherwise ffmpeg will break on certain files due to default value is 0.
 07077            var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
 7078
 07079            if (state.MediaSource.AnalyzeDurationMs > 0)
 7080            {
 07081                analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToStr
 7082            }
 07083            else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
 7084            {
 07085                analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
 7086            }
 7087
 07088            if (!string.IsNullOrEmpty(analyzeDurationArgument))
 7089            {
 07090                inputModifier += " " + analyzeDurationArgument;
 7091            }
 7092
 07093            inputModifier = inputModifier.Trim();
 7094
 7095            // Apply -probesize if configured
 07096            var ffmpegProbeSize = _config.GetFFmpegProbeSize();
 7097
 07098            if (!string.IsNullOrEmpty(ffmpegProbeSize))
 7099            {
 07100                inputModifier += $" -probesize {ffmpegProbeSize}";
 7101            }
 7102
 07103            var userAgentParam = GetUserAgentParam(state);
 7104
 07105            if (!string.IsNullOrEmpty(userAgentParam))
 7106            {
 07107                inputModifier += " " + userAgentParam;
 7108            }
 7109
 07110            inputModifier = inputModifier.Trim();
 7111
 07112            var refererParam = GetRefererParam(state);
 7113
 07114            if (!string.IsNullOrEmpty(refererParam))
 7115            {
 07116                inputModifier += " " + refererParam;
 7117            }
 7118
 07119            inputModifier = inputModifier.Trim();
 7120
 07121            inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions, segmentContainer);
 07122            inputModifier = inputModifier.Trim();
 7123
 07124            if (state.InputProtocol == MediaProtocol.Rtsp)
 7125            {
 07126                inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
 7127            }
 7128
 07129            if (!string.IsNullOrEmpty(state.InputAudioSync))
 7130            {
 07131                inputModifier += " -async " + state.InputAudioSync;
 7132            }
 7133
 07134            if (!string.IsNullOrEmpty(state.InputVideoSync))
 7135            {
 07136                inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion);
 7137            }
 7138
 07139            if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp)
 7140            {
 07141                inputModifier += " -re";
 7142            }
 07143            else if (encodingOptions.EnableSegmentDeletion
 07144                && state.VideoStream is not null
 07145                && state.TranscodingType == TranscodingJobType.Hls
 07146                && IsCopyCodec(state.OutputVideoCodec)
 07147                && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateOption)
 7148            {
 7149                // Set an input read rate limit 10x for using SegmentDeletion with stream-copy
 7150                // to prevent ffmpeg from exiting prematurely (due to fast drive)
 07151                inputModifier += " -readrate 10";
 7152            }
 7153
 07154            var flags = new List<string>();
 07155            if (state.IgnoreInputDts)
 7156            {
 07157                flags.Add("+igndts");
 7158            }
 7159
 07160            if (state.IgnoreInputIndex)
 7161            {
 07162                flags.Add("+ignidx");
 7163            }
 7164
 07165            if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
 7166            {
 07167                flags.Add("+genpts");
 7168            }
 7169
 07170            if (state.DiscardCorruptFramesInput)
 7171            {
 07172                flags.Add("+discardcorrupt");
 7173            }
 7174
 07175            if (state.EnableFastSeekInput)
 7176            {
 07177                flags.Add("+fastseek");
 7178            }
 7179
 07180            if (flags.Count > 0)
 7181            {
 07182                inputModifier += " -fflags " + string.Join(string.Empty, flags);
 7183            }
 7184
 07185            if (state.IsVideoRequest)
 7186            {
 07187                if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && encodingOpt
 7188                {
 07189                    var inputFormat = GetInputFormat(state.InputContainer);
 07190                    if (!string.IsNullOrEmpty(inputFormat))
 7191                    {
 07192                        inputModifier += " -f " + inputFormat;
 7193                    }
 7194                }
 7195            }
 7196
 07197            if (state.MediaSource.RequiresLooping)
 7198            {
 07199                inputModifier += " -stream_loop -1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2";
 7200            }
 7201
 07202            return inputModifier;
 7203        }
 7204
 7205        public void AttachMediaSourceInfo(
 7206            EncodingJobInfo state,
 7207            EncodingOptions encodingOptions,
 7208            MediaSourceInfo mediaSource,
 7209            string requestedUrl)
 7210        {
 07211            ArgumentNullException.ThrowIfNull(state);
 7212
 07213            ArgumentNullException.ThrowIfNull(mediaSource);
 7214
 07215            var path = mediaSource.Path;
 07216            var protocol = mediaSource.Protocol;
 7217
 07218            if (!string.IsNullOrEmpty(mediaSource.EncoderPath) && mediaSource.EncoderProtocol.HasValue)
 7219            {
 07220                path = mediaSource.EncoderPath;
 07221                protocol = mediaSource.EncoderProtocol.Value;
 7222            }
 7223
 07224            state.MediaPath = path;
 07225            state.InputProtocol = protocol;
 07226            state.InputContainer = mediaSource.Container;
 07227            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07228            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 7229
 07230            state.IsoType = mediaSource.IsoType;
 7231
 07232            if (mediaSource.Timestamp.HasValue)
 7233            {
 07234                state.InputTimestamp = mediaSource.Timestamp.Value;
 7235            }
 7236
 07237            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07238            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 07239            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
 7240
 07241            if ((state.ReadInputAtNativeFramerate && !state.IsSegmentedLiveStream)
 07242                || (mediaSource.Protocol == MediaProtocol.File
 07243                && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)))
 7244            {
 07245                state.InputVideoSync = "-1";
 07246                state.InputAudioSync = "1";
 7247            }
 7248
 07249            if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase)
 07250                || string.Equals(mediaSource.Container, "asf", StringComparison.OrdinalIgnoreCase))
 7251            {
 7252                // Seeing some stuttering when transcoding wma to audio-only HLS
 07253                state.InputAudioSync = "1";
 7254            }
 7255
 07256            var mediaStreams = mediaSource.MediaStreams;
 7257
 07258            if (state.IsVideoRequest)
 7259            {
 07260                var videoRequest = state.BaseRequest;
 7261
 07262                if (string.IsNullOrEmpty(videoRequest.VideoCodec))
 7263                {
 07264                    if (string.IsNullOrEmpty(requestedUrl))
 7265                    {
 07266                        requestedUrl = "test." + videoRequest.Container;
 7267                    }
 7268
 07269                    videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
 7270                }
 7271
 07272                state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
 07273                state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Su
 07274                state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
 07275                state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
 7276
 07277                if (state.SubtitleStream is not null && !state.SubtitleStream.IsExternal)
 7278                {
 07279                    state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !
 7280                }
 7281
 07282                EnforceResolutionLimit(state);
 7283
 07284                NormalizeSubtitleEmbed(state);
 7285            }
 7286            else
 7287            {
 07288                state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
 7289            }
 7290
 07291            state.MediaSource = mediaSource;
 7292
 07293            var request = state.BaseRequest;
 07294            var supportedAudioCodecs = state.SupportedAudioCodecs;
 07295            if (request is not null && supportedAudioCodecs is not null && supportedAudioCodecs.Length > 0)
 7296            {
 07297                var supportedAudioCodecsList = supportedAudioCodecs.ToList();
 7298
 07299                ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
 7300
 07301                state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray();
 7302
 07303                request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(_mediaEncoder.CanEncodeToAudioCodec)
 07304                    ?? state.SupportedAudioCodecs.FirstOrDefault();
 7305            }
 7306
 07307            var supportedVideoCodecs = state.SupportedVideoCodecs;
 07308            if (request is not null && supportedVideoCodecs is not null && supportedVideoCodecs.Length > 0)
 7309            {
 07310                var supportedVideoCodecsList = supportedVideoCodecs.ToList();
 7311
 07312                ShiftVideoCodecsIfNeeded(supportedVideoCodecsList, encodingOptions);
 7313
 07314                state.SupportedVideoCodecs = supportedVideoCodecsList.ToArray();
 7315
 07316                request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
 7317            }
 07318        }
 7319
 7320        private void ShiftAudioCodecsIfNeeded(List<string> audioCodecs, MediaStream audioStream)
 7321        {
 7322            // No need to shift if there is only one supported audio codec.
 07323            if (audioCodecs.Count < 2)
 7324            {
 07325                return;
 7326            }
 7327
 07328            var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
 07329            var shiftAudioCodecs = new List<string>();
 07330            if (inputChannels >= 6)
 7331            {
 7332                // DTS and TrueHD are not supported by HLS
 7333                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07334                shiftAudioCodecs.Add("dts");
 07335                shiftAudioCodecs.Add("truehd");
 7336            }
 7337            else
 7338            {
 7339                // Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
 7340                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07341                shiftAudioCodecs.Add("ac3");
 07342                shiftAudioCodecs.Add("eac3");
 7343            }
 7344
 07345            if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7346            {
 07347                return;
 7348            }
 7349
 07350            while (shiftAudioCodecs.Contains(audioCodecs[0], StringComparison.OrdinalIgnoreCase))
 7351            {
 07352                var removed = audioCodecs[0];
 07353                audioCodecs.RemoveAt(0);
 07354                audioCodecs.Add(removed);
 7355            }
 07356        }
 7357
 7358        private void ShiftVideoCodecsIfNeeded(List<string> videoCodecs, EncodingOptions encodingOptions)
 7359        {
 7360            // No need to shift if there is only one supported video codec.
 07361            if (videoCodecs.Count < 2)
 7362            {
 07363                return;
 7364            }
 7365
 7366            // Shift codecs to the end of list if it's not allowed.
 07367            var shiftVideoCodecs = new List<string>();
 07368            if (!encodingOptions.AllowHevcEncoding)
 7369            {
 07370                shiftVideoCodecs.Add("hevc");
 07371                shiftVideoCodecs.Add("h265");
 7372            }
 7373
 07374            if (!encodingOptions.AllowAv1Encoding)
 7375            {
 07376                shiftVideoCodecs.Add("av1");
 7377            }
 7378
 07379            if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7380            {
 07381                return;
 7382            }
 7383
 07384            while (shiftVideoCodecs.Contains(videoCodecs[0], StringComparison.OrdinalIgnoreCase))
 7385            {
 07386                var removed = videoCodecs[0];
 07387                videoCodecs.RemoveAt(0);
 07388                videoCodecs.Add(removed);
 7389            }
 07390        }
 7391
 7392        private void NormalizeSubtitleEmbed(EncodingJobInfo state)
 7393        {
 07394            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7395            {
 07396                return;
 7397            }
 7398
 7399            // This is tricky to remux in, after converting to dvdsub it's not positioned correctly
 7400            // Therefore, let's just burn it in
 07401            if (string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 7402            {
 07403                state.SubtitleDeliveryMethod = SubtitleDeliveryMethod.Encode;
 7404            }
 07405        }
 7406
 7407        public string GetSubtitleEmbedArguments(EncodingJobInfo state)
 7408        {
 07409            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7410            {
 07411                return string.Empty;
 7412            }
 7413
 07414            var format = state.SupportedSubtitleCodecs.FirstOrDefault();
 7415            string codec;
 7416
 07417            if (string.IsNullOrEmpty(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.Ordin
 7418            {
 07419                codec = "copy";
 7420            }
 7421            else
 7422            {
 07423                codec = format;
 7424            }
 7425
 07426            return " -codec:s:0 " + codec + " -disposition:s:0 default";
 7427        }
 7428
 7429        public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, Encoder
 7430        {
 7431            // Get the output codec name
 07432            var videoCodec = GetVideoEncoder(state, encodingOptions);
 7433
 07434            var format = string.Empty;
 07435            var keyFrame = string.Empty;
 07436            var outputPath = state.OutputFilePath;
 7437
 07438            if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase)
 07439                && state.BaseRequest.Context == EncodingContext.Streaming)
 7440            {
 7441                // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
 07442                format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov";
 7443            }
 7444
 07445            var threads = GetNumberOfThreads(state, encodingOptions, videoCodec);
 7446
 07447            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7448
 07449            return string.Format(
 07450                CultureInfo.InvariantCulture,
 07451                "{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
 07452                inputModifier,
 07453                GetInputArgument(state, encodingOptions, null),
 07454                keyFrame,
 07455                GetMapArgs(state),
 07456                GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset),
 07457                threads,
 07458                GetProgressiveVideoAudioArguments(state, encodingOptions),
 07459                GetSubtitleEmbedArguments(state),
 07460                format,
 07461                outputPath).Trim();
 7462        }
 7463
 7464        public string GetOutputFFlags(EncodingJobInfo state)
 7465        {
 07466            var flags = new List<string>();
 07467            if (state.GenPtsOutput)
 7468            {
 07469                flags.Add("+genpts");
 7470            }
 7471
 07472            if (flags.Count > 0)
 7473            {
 07474                return " -fflags " + string.Join(string.Empty, flags);
 7475            }
 7476
 07477            return string.Empty;
 7478        }
 7479
 7480        public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoC
 7481        {
 07482            var args = "-codec:v:0 " + videoCodec;
 7483
 07484            if (state.BaseRequest.EnableMpegtsM2TsMode)
 7485            {
 07486                args += " -mpegts_m2ts_mode 1";
 7487            }
 7488
 07489            if (IsCopyCodec(videoCodec))
 7490            {
 07491                if (state.VideoStream is not null
 07492                    && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
 07493                    && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
 7494                {
 07495                    string bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Video);
 07496                    if (!string.IsNullOrEmpty(bitStreamArgs))
 7497                    {
 07498                        args += " " + bitStreamArgs;
 7499                    }
 7500                }
 7501
 07502                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7503                {
 07504                    args += " -copyts -avoid_negative_ts disabled -start_at_zero";
 7505                }
 7506
 07507                if (!state.RunTimeTicks.HasValue)
 7508                {
 07509                    args += " -fflags +genpts";
 7510                }
 7511            }
 7512            else
 7513            {
 07514                var keyFrameArg = string.Format(
 07515                    CultureInfo.InvariantCulture,
 07516                    " -force_key_frames \"expr:gte(t,n_forced*{0})\"",
 07517                    5);
 7518
 07519                args += keyFrameArg;
 7520
 07521                var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream &&
 7522
 07523                var hasCopyTs = false;
 7524
 7525                // video processing filters.
 07526                var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
 7527
 07528                var negativeMapArgs = GetNegativeMapArgsByFilters(state, videoProcessParam);
 7529
 07530                args = negativeMapArgs + args + videoProcessParam;
 7531
 07532                hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
 7533
 07534                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7535                {
 07536                    if (!hasCopyTs)
 7537                    {
 07538                        args += " -copyts";
 7539                    }
 7540
 07541                    args += " -avoid_negative_ts disabled";
 7542
 07543                    if (!(state.SubtitleStream is not null && state.SubtitleStream.IsExternal && !state.SubtitleStream.I
 7544                    {
 07545                        args += " -start_at_zero";
 7546                    }
 7547                }
 7548
 07549                var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset);
 7550
 07551                if (!string.IsNullOrEmpty(qualityParam))
 7552                {
 07553                    args += " " + qualityParam.Trim();
 7554                }
 7555            }
 7556
 07557            if (!string.IsNullOrEmpty(state.OutputVideoSync))
 7558            {
 07559                args += GetVideoSyncOption(state.OutputVideoSync, _mediaEncoder.EncoderVersion);
 7560            }
 7561
 07562            args += GetOutputFFlags(state);
 7563
 07564            return args;
 7565        }
 7566
 7567        public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions)
 7568        {
 7569            // If the video doesn't have an audio stream, return a default.
 07570            if (state.AudioStream is null && state.VideoStream is not null)
 7571            {
 07572                return string.Empty;
 7573            }
 7574
 7575            // Get the output codec name
 07576            var codec = GetAudioEncoder(state);
 7577
 07578            var args = "-codec:a:0 " + codec;
 7579
 07580            if (IsCopyCodec(codec))
 7581            {
 07582                return args;
 7583            }
 7584
 07585            var channels = state.OutputAudioChannels;
 7586
 07587            var useDownMixAlgorithm = state.AudioStream is not null
 07588                                      && DownMixAlgorithmsHelper.AlgorithmFilterStrings.ContainsKey((encodingOptions.Dow
 7589
 07590            if (channels.HasValue && !useDownMixAlgorithm)
 7591            {
 07592                args += " -ac " + channels.Value;
 7593            }
 7594
 07595            var bitrate = state.OutputAudioBitrate;
 07596            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
 7597            {
 07598                var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2);
 07599                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7600                {
 07601                    args += vbrParam;
 7602                }
 7603                else
 7604                {
 07605                    args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
 7606                }
 7607            }
 7608
 07609            if (state.OutputAudioSampleRate.HasValue)
 7610            {
 07611                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
 7612            }
 7613
 07614            args += GetAudioFilterParam(state, encodingOptions);
 7615
 07616            return args;
 7617        }
 7618
 7619        public string GetProgressiveAudioFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string 
 7620        {
 07621            var audioTranscodeParams = new List<string>();
 7622
 07623            var bitrate = state.OutputAudioBitrate;
 07624            var channels = state.OutputAudioChannels;
 07625            var outputCodec = state.OutputAudioCodec;
 7626
 07627            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
 7628            {
 07629                var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2);
 07630                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7631                {
 07632                    audioTranscodeParams.Add(vbrParam);
 7633                }
 7634                else
 7635                {
 07636                    audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
 7637                }
 7638            }
 7639
 07640            if (channels.HasValue)
 7641            {
 07642                audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)
 7643            }
 7644
 07645            if (!string.IsNullOrEmpty(outputCodec))
 7646            {
 07647                audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
 7648            }
 7649
 07650            if (GetAudioEncoder(state).StartsWith("pcm_", StringComparison.Ordinal))
 7651            {
 07652                audioTranscodeParams.Add(string.Concat("-f ", GetAudioEncoder(state).AsSpan(4)));
 07653                audioTranscodeParams.Add("-ar " + state.BaseRequest.AudioBitRate);
 7654            }
 7655
 07656            if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
 7657            {
 7658                // opus only supports specific sampling rates
 07659                var sampleRate = state.OutputAudioSampleRate;
 07660                if (sampleRate.HasValue)
 7661                {
 07662                    var sampleRateValue = sampleRate.Value switch
 07663                    {
 07664                        <= 8000 => 8000,
 07665                        <= 12000 => 12000,
 07666                        <= 16000 => 16000,
 07667                        <= 24000 => 24000,
 07668                        _ => 48000
 07669                    };
 7670
 07671                    audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
 7672                }
 7673            }
 7674
 7675            // Copy the movflags from GetProgressiveVideoFullCommandLine
 7676            // See #9248 and the associated PR for why this is needed
 07677            if (_mp4ContainerNames.Contains(state.OutputContainer))
 7678            {
 07679                audioTranscodeParams.Add("-movflags empty_moov+delay_moov");
 7680            }
 7681
 07682            var threads = GetNumberOfThreads(state, encodingOptions, null);
 7683
 07684            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7685
 07686            return string.Format(
 07687                CultureInfo.InvariantCulture,
 07688                "{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"",
 07689                inputModifier,
 07690                GetInputArgument(state, encodingOptions, null),
 07691                threads,
 07692                " -vn",
 07693                string.Join(' ', audioTranscodeParams),
 07694                outputPath,
 07695                string.Empty,
 07696                string.Empty,
 07697                string.Empty).Trim();
 7698        }
 7699
 7700        public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
 7701        {
 07702            var index = 0;
 07703            var length = mediaStreams.Count;
 7704
 07705            for (var i = 0; i < length; i++)
 7706            {
 07707                var currentMediaStream = mediaStreams[i];
 07708                if (currentMediaStream == streamToFind)
 7709                {
 07710                    return index;
 7711                }
 7712
 07713                if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
 7714                {
 07715                    index++;
 7716                }
 7717            }
 7718
 07719            return -1;
 7720        }
 7721
 7722        public static bool IsCopyCodec(string codec)
 7723        {
 07724            return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
 7725        }
 7726
 7727        private static bool ShouldEncodeSubtitle(EncodingJobInfo state)
 7728        {
 07729            return state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
 07730                   || (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec));
 7731        }
 7732
 7733        public static string GetVideoSyncOption(string videoSync, Version encoderVersion)
 7734        {
 07735            if (string.IsNullOrEmpty(videoSync))
 7736            {
 07737                return string.Empty;
 7738            }
 7739
 07740            if (encoderVersion >= new Version(5, 1))
 7741            {
 07742                if (int.TryParse(videoSync, CultureInfo.InvariantCulture, out var vsync))
 7743                {
 07744                    return vsync switch
 07745                    {
 07746                        -1 => " -fps_mode auto",
 07747                        0 => " -fps_mode passthrough",
 07748                        1 => " -fps_mode cfr",
 07749                        2 => " -fps_mode vfr",
 07750                        _ => string.Empty
 07751                    };
 7752                }
 7753
 07754                return string.Empty;
 7755            }
 7756
 7757            // -vsync is deprecated in FFmpeg 5.1 and will be removed in the future.
 07758            return $" -vsync {videoSync}";
 7759        }
 7760    }
 7761}

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)