< 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: 3697
Coverable lines: 3723
Total lines: 7782
Line coverage: 0.6%
Branch coverage
0%
Covered branches: 0
Total branches: 3703
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%160021260%
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%208801440%
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%3906620%
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
 02379                var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.Ordin
 2380
 2381                // If the client does not support DOVI and the video stream is DOVI without fallback, we should not copy
 02382                if (!requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVI)
 2383                {
 02384                    return false;
 2385                }
 2386
 02387                if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreC
 02388                     && !((requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10)
 02389                            || (requestHasHLG && videoStream.VideoRangeType == VideoRangeType.DOVIWithHLG)
 02390                            || (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)
 02391                            || (requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.HDR10Plus)))
 2392                {
 2393                    // If the video stream is in a static HDR format, don't allow copy if the client does not support HD
 02394                    if (videoStream.VideoRangeType is VideoRangeType.HDR10 or VideoRangeType.HLG)
 2395                    {
 02396                        return false;
 2397                    }
 2398
 2399                    // Check complicated cases where we need to remove dynamic metadata
 2400                    // Conservatively refuse to copy if the encoder can't remove dynamic metadata,
 2401                    // but a removal is required for compatability reasons.
 02402                    var dynamicHdrMetadataRemovalPlan = ShouldRemoveDynamicHdrMetadata(state);
 02403                    if (!CanEncoderRemoveDynamicHdrMetadata(dynamicHdrMetadataRemovalPlan, videoStream))
 2404                    {
 02405                        return false;
 2406                    }
 2407                }
 2408            }
 2409
 2410            // Video width must fall within requested value
 02411            if (request.MaxWidth.HasValue
 02412                && (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
 2413            {
 02414                return false;
 2415            }
 2416
 2417            // Video height must fall within requested value
 02418            if (request.MaxHeight.HasValue
 02419                && (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value))
 2420            {
 02421                return false;
 2422            }
 2423
 2424            // Video framerate must fall within requested value
 02425            var requestedFramerate = request.MaxFramerate ?? request.Framerate;
 02426            if (requestedFramerate.HasValue)
 2427            {
 02428                var videoFrameRate = videoStream.ReferenceFrameRate;
 2429
 2430                // Add a little tolerance to the framerate check because some videos might record a framerate
 2431                // that is slightly greater than the intended framerate, but the device can still play it correctly.
 2432                // 0.05 fps tolerance should be safe enough.
 02433                if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value + 0.05f)
 2434                {
 02435                    return false;
 2436                }
 2437            }
 2438
 2439            // Video bitrate must fall within requested value
 02440            if (request.VideoBitRate.HasValue
 02441                && (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value))
 2442            {
 2443                // For LiveTV that has no bitrate, let's try copy if other conditions are met
 02444                if (string.IsNullOrWhiteSpace(request.LiveStreamId) || videoStream.BitRate.HasValue)
 2445                {
 02446                    return false;
 2447                }
 2448            }
 2449
 02450            var maxBitDepth = state.GetRequestedVideoBitDepth(videoStream.Codec);
 02451            if (maxBitDepth.HasValue)
 2452            {
 02453                if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > maxBitDepth.Value)
 2454                {
 02455                    return false;
 2456                }
 2457            }
 2458
 02459            var maxRefFrames = state.GetRequestedMaxRefFrames(videoStream.Codec);
 02460            if (maxRefFrames.HasValue
 02461                && videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > maxRefFrames.Value)
 2462            {
 02463                return false;
 2464            }
 2465
 2466            // If a specific level was requested, the source must match or be less than
 02467            var level = state.GetRequestedLevel(videoStream.Codec);
 02468            if (double.TryParse(level, CultureInfo.InvariantCulture, out var requestLevel))
 2469            {
 02470                if (!videoStream.Level.HasValue)
 2471                {
 2472                    // return false;
 2473                }
 2474
 02475                if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
 2476                {
 02477                    return false;
 2478                }
 2479            }
 2480
 02481            if (string.Equals(state.InputContainer, "avi", StringComparison.OrdinalIgnoreCase)
 02482                && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)
 02483                && !(videoStream.IsAVC ?? false))
 2484            {
 2485                // see Coach S01E01 - Kelly and the Professor(0).avi
 02486                return false;
 2487            }
 2488
 02489            return true;
 2490        }
 2491
 2492        public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, IEnumerable<string> supportedAudi
 2493        {
 02494            var request = state.BaseRequest;
 2495
 02496            if (!request.AllowAudioStreamCopy)
 2497            {
 02498                return false;
 2499            }
 2500
 02501            var maxBitDepth = state.GetRequestedAudioBitDepth(audioStream.Codec);
 02502            if (maxBitDepth.HasValue
 02503                && audioStream.BitDepth.HasValue
 02504                && audioStream.BitDepth.Value > maxBitDepth.Value)
 2505            {
 02506                return false;
 2507            }
 2508
 2509            // Source and target codecs must match
 02510            if (string.IsNullOrEmpty(audioStream.Codec)
 02511                || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparison.OrdinalIgnoreCase))
 2512            {
 02513                return false;
 2514            }
 2515
 2516            // Channels must fall within requested value
 02517            var channels = state.GetRequestedAudioChannels(audioStream.Codec);
 02518            if (channels.HasValue)
 2519            {
 02520                if (!audioStream.Channels.HasValue || audioStream.Channels.Value <= 0)
 2521                {
 02522                    return false;
 2523                }
 2524
 02525                if (audioStream.Channels.Value > channels.Value)
 2526                {
 02527                    return false;
 2528                }
 2529            }
 2530
 2531            // Sample rate must fall within requested value
 02532            if (request.AudioSampleRate.HasValue)
 2533            {
 02534                if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value <= 0)
 2535                {
 02536                    return false;
 2537                }
 2538
 02539                if (audioStream.SampleRate.Value > request.AudioSampleRate.Value)
 2540                {
 02541                    return false;
 2542                }
 2543            }
 2544
 2545            // Audio bitrate must fall within requested value
 02546            if (request.AudioBitRate.HasValue
 02547                && audioStream.BitRate.HasValue
 02548                && audioStream.BitRate.Value > request.AudioBitRate.Value)
 2549            {
 02550                return false;
 2551            }
 2552
 02553            return request.EnableAutoStreamCopy;
 2554        }
 2555
 2556        public int GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideo
 2557        {
 02558            var bitrate = request.VideoBitRate;
 2559
 02560            if (videoStream is not null)
 2561            {
 02562                var isUpscaling = request.Height.HasValue
 02563                    && videoStream.Height.HasValue
 02564                    && request.Height.Value > videoStream.Height.Value
 02565                    && request.Width.HasValue
 02566                    && videoStream.Width.HasValue
 02567                    && request.Width.Value > videoStream.Width.Value;
 2568
 2569                // Don't allow bitrate increases unless upscaling
 02570                if (!isUpscaling && bitrate.HasValue && videoStream.BitRate.HasValue)
 2571                {
 02572                    bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value);
 2573                }
 2574
 02575                if (bitrate.HasValue)
 2576                {
 02577                    var inputVideoCodec = videoStream.Codec;
 02578                    bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
 2579
 2580                    // If a max bitrate was requested, don't let the scaled bitrate exceed it
 02581                    if (request.VideoBitRate.HasValue)
 2582                    {
 02583                        bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
 2584                    }
 2585                }
 2586            }
 2587
 2588            // Cap the max target bitrate to intMax/2 to satisfy the bufsize=bitrate*2.
 02589            return Math.Min(bitrate ?? 0, int.MaxValue / 2);
 2590        }
 2591
 2592        private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
 2593        {
 2594            // these values were chosen from testing to improve low bitrate streams
 02595            if (sourceBitrate <= 2000000)
 2596            {
 02597                sourceBitrate = Convert.ToInt32(sourceBitrate * 2.5);
 2598            }
 02599            else if (sourceBitrate <= 3000000)
 2600            {
 02601                sourceBitrate *= 2;
 2602            }
 2603
 02604            var bitrate = Math.Min(sourceBitrate, requestedBitrate);
 2605
 02606            return bitrate;
 2607        }
 2608
 2609        private static double GetVideoBitrateScaleFactor(string codec)
 2610        {
 2611            // hevc & vp9 - 40% more efficient than h.264
 02612            if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 02613                || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
 02614                || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
 2615            {
 02616                return .6;
 2617            }
 2618
 2619            // av1 - 50% more efficient than h.264
 02620            if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 2621            {
 02622                return .5;
 2623            }
 2624
 02625            return 1;
 2626        }
 2627
 2628        public static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
 2629        {
 02630            var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
 02631            var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
 2632
 2633            // Don't scale the real bitrate lower than the requested bitrate
 02634            var scaleFactor = Math.Max(outputScaleFactor / inputScaleFactor, 1);
 2635
 02636            if (bitrate <= 500000)
 2637            {
 02638                scaleFactor = Math.Max(scaleFactor, 4);
 2639            }
 02640            else if (bitrate <= 1000000)
 2641            {
 02642                scaleFactor = Math.Max(scaleFactor, 3);
 2643            }
 02644            else if (bitrate <= 2000000)
 2645            {
 02646                scaleFactor = Math.Max(scaleFactor, 2.5);
 2647            }
 02648            else if (bitrate <= 3000000)
 2649            {
 02650                scaleFactor = Math.Max(scaleFactor, 2);
 2651            }
 02652            else if (bitrate >= 30000000)
 2653            {
 2654                // Don't scale beyond 30Mbps, it is hardly visually noticeable for most codecs with our prefer speed enc
 2655                // and will cause extremely high bitrate to be used for av1->h264 transcoding that will overload clients
 02656                scaleFactor = 1;
 2657            }
 2658
 02659            return Convert.ToInt32(scaleFactor * bitrate);
 2660        }
 2661
 2662        public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChanne
 2663        {
 02664            return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels);
 2665        }
 2666
 2667        public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudio
 2668        {
 02669            if (audioStream is null)
 2670            {
 02671                return null;
 2672            }
 2673
 02674            var inputChannels = audioStream.Channels ?? 0;
 02675            var outputChannels = outputAudioChannels ?? 0;
 02676            var bitrate = audioBitRate ?? int.MaxValue;
 2677
 02678            if (string.IsNullOrEmpty(audioCodec)
 02679                || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
 02680                || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
 02681                || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
 02682                || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
 02683                || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
 02684                || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
 2685            {
 02686                return (inputChannels, outputChannels) switch
 02687                {
 02688                    (>= 6, >= 6 or 0) => Math.Min(640000, bitrate),
 02689                    (> 0, > 0) => Math.Min(outputChannels * 128000, bitrate),
 02690                    (> 0, _) => Math.Min(inputChannels * 128000, bitrate),
 02691                    (_, _) => Math.Min(384000, bitrate)
 02692                };
 2693            }
 2694
 02695            if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
 02696                || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
 2697            {
 02698                return (inputChannels, outputChannels) switch
 02699                {
 02700                    (>= 6, >= 6 or 0) => Math.Min(768000, bitrate),
 02701                    (> 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
 02702                    (> 0, _) => Math.Min(inputChannels * 136000, bitrate),
 02703                    (_, _) => Math.Min(672000, bitrate)
 02704                };
 2705            }
 2706
 2707            // Empty bitrate area is not allow on iOS
 2708            // Default audio bitrate to 128K per channel if we don't have codec specific defaults
 2709            // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
 02710            return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
 2711        }
 2712
 2713        public string GetAudioVbrModeParam(string encoder, int bitrate, int channels)
 2714        {
 02715            var bitratePerChannel = bitrate / Math.Max(channels, 1);
 02716            if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
 2717            {
 02718                return " -vbr:a " + bitratePerChannel switch
 02719                {
 02720                    < 32000 => "1",
 02721                    < 48000 => "2",
 02722                    < 64000 => "3",
 02723                    < 96000 => "4",
 02724                    _ => "5"
 02725                };
 2726            }
 2727
 02728            if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase))
 2729            {
 2730                // lame's VBR is only good for a certain bitrate range
 2731                // For very low and very high bitrate, use abr mode
 02732                if (bitratePerChannel is < 122500 and > 48000)
 2733                {
 02734                    return " -qscale:a " + bitratePerChannel switch
 02735                    {
 02736                        < 64000 => "6",
 02737                        < 88000 => "4",
 02738                        < 112000 => "2",
 02739                        _ => "0"
 02740                    };
 2741                }
 2742
 02743                return " -abr:a 1" + " -b:a " + bitrate;
 2744            }
 2745
 02746            if (string.Equals(encoder, "aac_at", StringComparison.OrdinalIgnoreCase))
 2747            {
 2748                // aac_at's CVBR mode
 02749                return " -aac_at_mode:a 2" + " -b:a " + bitrate;
 2750            }
 2751
 02752            if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase))
 2753            {
 02754                return " -qscale:a " + bitratePerChannel switch
 02755                {
 02756                    < 40000 => "0",
 02757                    < 56000 => "2",
 02758                    < 80000 => "4",
 02759                    < 112000 => "6",
 02760                    _ => "8"
 02761                };
 2762            }
 2763
 02764            return null;
 2765        }
 2766
 2767        public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
 2768        {
 02769            var channels = state.OutputAudioChannels;
 2770
 02771            var filters = new List<string>();
 2772
 02773            if (channels is 2 && state.AudioStream?.Channels is > 2)
 2774            {
 02775                var hasDownMixFilter = DownMixAlgorithmsHelper.AlgorithmFilterStrings.TryGetValue((encodingOptions.DownM
 02776                if (hasDownMixFilter)
 2777                {
 02778                    filters.Add(downMixFilterString);
 2779                }
 2780
 02781                if (!encodingOptions.DownMixAudioBoost.Equals(1))
 2782                {
 02783                    filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
 2784                }
 2785            }
 2786
 02787            var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
 02788            if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && ShouldEncodeSubtitle(st
 2789            {
 02790                var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds;
 2791
 02792                filters.Add(
 02793                    string.Format(
 02794                        CultureInfo.InvariantCulture,
 02795                        "asetpts=PTS-{0}/TB",
 02796                        Math.Round(seconds)));
 2797            }
 2798
 02799            if (filters.Count > 0)
 2800            {
 02801                return " -af \"" + string.Join(',', filters) + "\"";
 2802            }
 2803
 02804            return string.Empty;
 2805        }
 2806
 2807        /// <summary>
 2808        /// Gets the number of audio channels to specify on the command line.
 2809        /// </summary>
 2810        /// <param name="state">The state.</param>
 2811        /// <param name="audioStream">The audio stream.</param>
 2812        /// <param name="outputAudioCodec">The output audio codec.</param>
 2813        /// <returns>System.Nullable{System.Int32}.</returns>
 2814        public int? GetNumAudioChannelsParam(EncodingJobInfo state, MediaStream audioStream, string outputAudioCodec)
 2815        {
 02816            if (audioStream is null)
 2817            {
 02818                return null;
 2819            }
 2820
 02821            var request = state.BaseRequest;
 2822
 02823            var codec = outputAudioCodec ?? string.Empty;
 2824
 02825            int? resultChannels = state.GetRequestedAudioChannels(codec);
 2826
 02827            var inputChannels = audioStream.Channels;
 2828
 02829            if (inputChannels > 0)
 2830            {
 02831                resultChannels = inputChannels < resultChannels ? inputChannels : resultChannels ?? inputChannels;
 2832            }
 2833
 02834            var isTranscodingAudio = !IsCopyCodec(codec);
 2835
 02836            if (isTranscodingAudio)
 2837            {
 02838                var audioEncoder = GetAudioEncoder(state);
 02839                if (!_audioTranscodeChannelLookup.TryGetValue(audioEncoder, out var transcoderChannelLimit))
 2840                {
 2841                    // Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many c
 02842                    transcoderChannelLimit = 8;
 2843                }
 2844
 2845                // Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelL
 02846                resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? tr
 2847
 02848                if (request.TranscodingMaxAudioChannels < resultChannels)
 2849                {
 02850                    resultChannels = request.TranscodingMaxAudioChannels;
 2851                }
 2852
 2853                // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
 2854                // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_d
 02855                if (state.TranscodingType != TranscodingJobType.Progressive
 02856                    && ((resultChannels > 2 && resultChannels < 6) || resultChannels == 7))
 2857                {
 2858                    // We can let FFMpeg supply an extra LFE channel for 5ch and 7ch to make them 5.1 and 7.1
 02859                    if (resultChannels == 5)
 2860                    {
 02861                        resultChannels = 6;
 2862                    }
 02863                    else if (resultChannels == 7)
 2864                    {
 02865                        resultChannels = 8;
 2866                    }
 2867                    else
 2868                    {
 2869                        // For other weird layout, just downmix to stereo for compatibility
 02870                        resultChannels = 2;
 2871                    }
 2872                }
 2873            }
 2874
 02875            return resultChannels;
 2876        }
 2877
 2878        /// <summary>
 2879        /// Enforces the resolution limit.
 2880        /// </summary>
 2881        /// <param name="state">The state.</param>
 2882        public void EnforceResolutionLimit(EncodingJobInfo state)
 2883        {
 02884            var videoRequest = state.BaseRequest;
 2885
 2886            // Switch the incoming params to be ceilings rather than fixed values
 02887            videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
 02888            videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
 2889
 02890            videoRequest.Width = null;
 02891            videoRequest.Height = null;
 02892        }
 2893
 2894        /// <summary>
 2895        /// Gets the fast seek command line parameter.
 2896        /// </summary>
 2897        /// <param name="state">The state.</param>
 2898        /// <param name="options">The options.</param>
 2899        /// <param name="segmentContainer">Segment Container.</param>
 2900        /// <returns>System.String.</returns>
 2901        /// <value>The fast seek command line parameter.</value>
 2902        public string GetFastSeekCommandLineParameter(EncodingJobInfo state, EncodingOptions options, string segmentCont
 2903        {
 02904            var time = state.BaseRequest.StartTimeTicks ?? 0;
 02905            var maxTime = state.RunTimeTicks ?? 0;
 02906            var seekParam = string.Empty;
 2907
 02908            if (time > 0)
 2909            {
 2910                // For direct streaming/remuxing, we seek at the exact position of the keyframe
 2911                // However, ffmpeg will seek to previous keyframe when the exact time is the input
 2912                // Workaround this by adding 0.5s offset to the seeking time to get the exact keyframe on most videos.
 2913                // This will help subtitle syncing.
 02914                var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCod
 02915                var seekTick = isHlsRemuxing ? time + 5000000L : time;
 2916
 2917                // Seeking beyond EOF makes no sense in transcoding. Clamp the seekTick value to
 2918                // [0, RuntimeTicks - 5.0s], so that the muxer gets packets and avoid error codes.
 02919                if (maxTime > 0)
 2920                {
 02921                    seekTick = Math.Clamp(seekTick, 0, Math.Max(maxTime - 50000000L, 0));
 2922                }
 2923
 02924                seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(seekT
 2925
 02926                if (state.IsVideoRequest)
 2927                {
 02928                    var outputVideoCodec = GetVideoEncoder(state, options);
 02929                    var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
 2930
 2931                    // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking
 2932                    // Disable -noaccurate_seek on mpegts container due to the timestamps issue on some clients,
 2933                    // but it's still required for fMP4 container otherwise the audio can't be synced to the video.
 02934                    if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase)
 02935                        && !string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase)
 02936                        && state.TranscodingType != TranscodingJobType.Progressive
 02937                        && !state.EnableBreakOnNonKeyFrames(outputVideoCodec)
 02938                        && (state.BaseRequest.StartTimeTicks ?? 0) > 0)
 2939                    {
 02940                        seekParam += " -noaccurate_seek";
 2941                    }
 2942                }
 2943            }
 2944
 02945            return seekParam;
 2946        }
 2947
 2948        /// <summary>
 2949        /// Gets the map args.
 2950        /// </summary>
 2951        /// <param name="state">The state.</param>
 2952        /// <returns>System.String.</returns>
 2953        public string GetMapArgs(EncodingJobInfo state)
 2954        {
 2955            // If we don't have known media info
 2956            // If input is video, use -sn to drop subtitles
 2957            // Otherwise just return empty
 02958            if (state.VideoStream is null && state.AudioStream is null)
 2959            {
 02960                return state.IsInputVideo ? "-sn" : string.Empty;
 2961            }
 2962
 2963            // We have media info, but we don't know the stream index
 02964            if (state.VideoStream is not null && state.VideoStream.Index == -1)
 2965            {
 02966                return "-sn";
 2967            }
 2968
 2969            // We have media info, but we don't know the stream index
 02970            if (state.AudioStream is not null && state.AudioStream.Index == -1)
 2971            {
 02972                return state.IsInputVideo ? "-sn" : string.Empty;
 2973            }
 2974
 02975            var args = string.Empty;
 2976
 02977            if (state.VideoStream is not null)
 2978            {
 02979                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 2980
 02981                args += string.Format(
 02982                    CultureInfo.InvariantCulture,
 02983                    "-map 0:{0}",
 02984                    videoStreamIndex);
 2985            }
 2986            else
 2987            {
 2988                // No known video stream
 02989                args += "-vn";
 2990            }
 2991
 02992            if (state.AudioStream is not null)
 2993            {
 02994                int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
 02995                if (state.AudioStream.IsExternal)
 2996                {
 02997                    bool hasExternalGraphicsSubs = state.SubtitleStream is not null
 02998                        && ShouldEncodeSubtitle(state)
 02999                        && state.SubtitleStream.IsExternal
 03000                        && !state.SubtitleStream.IsTextSubtitleStream;
 03001                    int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
 3002
 03003                    args += string.Format(
 03004                        CultureInfo.InvariantCulture,
 03005                        " -map {0}:{1}",
 03006                        externalAudioMapIndex,
 03007                        audioStreamIndex);
 3008                }
 3009                else
 3010                {
 03011                    args += string.Format(
 03012                        CultureInfo.InvariantCulture,
 03013                        " -map 0:{0}",
 03014                        audioStreamIndex);
 3015                }
 3016            }
 3017            else
 3018            {
 03019                args += " -map -0:a";
 3020            }
 3021
 03022            var subtitleMethod = state.SubtitleDeliveryMethod;
 03023            if (state.SubtitleStream is null || subtitleMethod == SubtitleDeliveryMethod.Hls)
 3024            {
 03025                args += " -map -0:s";
 3026            }
 03027            else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
 3028            {
 03029                int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3030
 03031                args += string.Format(
 03032                    CultureInfo.InvariantCulture,
 03033                    " -map 0:{0}",
 03034                    subtitleStreamIndex);
 3035            }
 03036            else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
 3037            {
 03038                int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3039
 03040                args += string.Format(
 03041                    CultureInfo.InvariantCulture,
 03042                    " -map 1:{0} -sn",
 03043                    externalSubtitleStreamIndex);
 3044            }
 3045
 03046            return args;
 3047        }
 3048
 3049        /// <summary>
 3050        /// Gets the negative map args by filters.
 3051        /// </summary>
 3052        /// <param name="state">The state.</param>
 3053        /// <param name="videoProcessFilters">The videoProcessFilters.</param>
 3054        /// <returns>System.String.</returns>
 3055        public string GetNegativeMapArgsByFilters(EncodingJobInfo state, string videoProcessFilters)
 3056        {
 03057            string args = string.Empty;
 3058
 3059            // http://ffmpeg.org/ffmpeg-all.html#toc-Complex-filtergraphs-1
 03060            if (state.VideoStream is not null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordina
 3061            {
 03062                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 3063
 03064                args += string.Format(
 03065                    CultureInfo.InvariantCulture,
 03066                    "-map -0:{0} ",
 03067                    videoStreamIndex);
 3068            }
 3069
 03070            return args;
 3071        }
 3072
 3073        /// <summary>
 3074        /// Determines which stream will be used for playback.
 3075        /// </summary>
 3076        /// <param name="allStream">All stream.</param>
 3077        /// <param name="desiredIndex">Index of the desired.</param>
 3078        /// <param name="type">The type.</param>
 3079        /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
 3080        /// <returns>MediaStream.</returns>
 3081        public MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, b
 3082        {
 03083            var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
 3084
 03085            if (desiredIndex.HasValue)
 3086            {
 03087                var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
 3088
 03089                if (stream is not null)
 3090                {
 03091                    return stream;
 3092                }
 3093            }
 3094
 03095            if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
 3096            {
 03097                return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
 03098                       streams.FirstOrDefault();
 3099            }
 3100
 3101            // Just return the first one
 03102            return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
 3103        }
 3104
 3105        public static (int? Width, int? Height) GetFixedOutputSize(
 3106            int? videoWidth,
 3107            int? videoHeight,
 3108            int? requestedWidth,
 3109            int? requestedHeight,
 3110            int? requestedMaxWidth,
 3111            int? requestedMaxHeight)
 3112        {
 03113            if (!videoWidth.HasValue && !requestedWidth.HasValue)
 3114            {
 03115                return (null, null);
 3116            }
 3117
 03118            if (!videoHeight.HasValue && !requestedHeight.HasValue)
 3119            {
 03120                return (null, null);
 3121            }
 3122
 03123            int inputWidth = Convert.ToInt32(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture);
 03124            int inputHeight = Convert.ToInt32(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture);
 03125            int outputWidth = requestedWidth ?? inputWidth;
 03126            int outputHeight = requestedHeight ?? inputHeight;
 3127
 3128            // Don't transcode video to bigger than 4k when using HW.
 03129            int maximumWidth = Math.Min(requestedMaxWidth ?? outputWidth, 4096);
 03130            int maximumHeight = Math.Min(requestedMaxHeight ?? outputHeight, 4096);
 3131
 03132            if (outputWidth > maximumWidth || outputHeight > maximumHeight)
 3133            {
 03134                var scaleW = (double)maximumWidth / outputWidth;
 03135                var scaleH = (double)maximumHeight / outputHeight;
 03136                var scale = Math.Min(scaleW, scaleH);
 03137                outputWidth = Math.Min(maximumWidth, Convert.ToInt32(outputWidth * scale));
 03138                outputHeight = Math.Min(maximumHeight, Convert.ToInt32(outputHeight * scale));
 3139            }
 3140
 03141            outputWidth = 2 * (outputWidth / 2);
 03142            outputHeight = 2 * (outputHeight / 2);
 3143
 03144            return (outputWidth, outputHeight);
 3145        }
 3146
 3147        public static bool IsScaleRatioSupported(
 3148            int? videoWidth,
 3149            int? videoHeight,
 3150            int? requestedWidth,
 3151            int? requestedHeight,
 3152            int? requestedMaxWidth,
 3153            int? requestedMaxHeight,
 3154            double? maxScaleRatio)
 3155        {
 03156            var (outWidth, outHeight) = GetFixedOutputSize(
 03157                videoWidth,
 03158                videoHeight,
 03159                requestedWidth,
 03160                requestedHeight,
 03161                requestedMaxWidth,
 03162                requestedMaxHeight);
 3163
 03164            if (!videoWidth.HasValue
 03165                 || !videoHeight.HasValue
 03166                 || !outWidth.HasValue
 03167                 || !outHeight.HasValue
 03168                 || !maxScaleRatio.HasValue
 03169                 || (maxScaleRatio.Value < 1.0f))
 3170            {
 03171                return false;
 3172            }
 3173
 03174            var minScaleRatio = 1.0f / maxScaleRatio;
 03175            var scaleRatioW = (double)outWidth / (double)videoWidth;
 03176            var scaleRatioH = (double)outHeight / (double)videoHeight;
 3177
 03178            if (scaleRatioW < minScaleRatio
 03179                || scaleRatioW > maxScaleRatio
 03180                || scaleRatioH < minScaleRatio
 03181                || scaleRatioH > maxScaleRatio)
 3182            {
 03183                return false;
 3184            }
 3185
 03186            return true;
 3187        }
 3188
 3189        public static string GetHwScaleFilter(
 3190            string hwScalePrefix,
 3191            string hwScaleSuffix,
 3192            string videoFormat,
 3193            bool swapOutputWandH,
 3194            int? videoWidth,
 3195            int? videoHeight,
 3196            int? requestedWidth,
 3197            int? requestedHeight,
 3198            int? requestedMaxWidth,
 3199            int? requestedMaxHeight)
 3200        {
 03201            var (outWidth, outHeight) = GetFixedOutputSize(
 03202                videoWidth,
 03203                videoHeight,
 03204                requestedWidth,
 03205                requestedHeight,
 03206                requestedMaxWidth,
 03207                requestedMaxHeight);
 3208
 03209            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03210            var isSizeFixed = !videoWidth.HasValue
 03211                || outWidth.Value != videoWidth.Value
 03212                || !videoHeight.HasValue
 03213                || outHeight.Value != videoHeight.Value;
 3214
 03215            var swpOutW = swapOutputWandH ? outHeight.Value : outWidth.Value;
 03216            var swpOutH = swapOutputWandH ? outWidth.Value : outHeight.Value;
 3217
 03218            var arg1 = isSizeFixed ? $"=w={swpOutW}:h={swpOutH}" : string.Empty;
 03219            var arg2 = isFormatFixed ? $"format={videoFormat}" : string.Empty;
 03220            if (isFormatFixed)
 3221            {
 03222                arg2 = (isSizeFixed ? ':' : '=') + arg2;
 3223            }
 3224
 03225            if (!string.IsNullOrEmpty(hwScaleSuffix) && (isSizeFixed || isFormatFixed))
 3226            {
 03227                return string.Format(
 03228                    CultureInfo.InvariantCulture,
 03229                    "{0}_{1}{2}{3}",
 03230                    hwScalePrefix ?? "scale",
 03231                    hwScaleSuffix,
 03232                    arg1,
 03233                    arg2);
 3234            }
 3235
 03236            return string.Empty;
 3237        }
 3238
 3239        public static string GetGraphicalSubPreProcessFilters(
 3240            int? videoWidth,
 3241            int? videoHeight,
 3242            int? subtitleWidth,
 3243            int? subtitleHeight,
 3244            int? requestedWidth,
 3245            int? requestedHeight,
 3246            int? requestedMaxWidth,
 3247            int? requestedMaxHeight)
 3248        {
 03249            var (outWidth, outHeight) = GetFixedOutputSize(
 03250                videoWidth,
 03251                videoHeight,
 03252                requestedWidth,
 03253                requestedHeight,
 03254                requestedMaxWidth,
 03255                requestedMaxHeight);
 3256
 03257            if (!outWidth.HasValue
 03258                || !outHeight.HasValue
 03259                || outWidth.Value <= 0
 03260                || outHeight.Value <= 0)
 3261            {
 03262                return string.Empty;
 3263            }
 3264
 3265            // Automatically add padding based on subtitle input
 03266            var filters = @"scale,scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:blac
 3267
 03268            if (subtitleWidth.HasValue
 03269                && subtitleHeight.HasValue
 03270                && subtitleWidth.Value > 0
 03271                && subtitleHeight.Value > 0)
 3272            {
 03273                var videoDar = (double)outWidth.Value / outHeight.Value;
 03274                var subtitleDar = (double)subtitleWidth.Value / subtitleHeight.Value;
 3275
 3276                // No need to add padding when DAR is the same -> 1080p PGSSUB on 2160p video
 03277                if (Math.Abs(videoDar - subtitleDar) < 0.01f)
 3278                {
 03279                    filters = @"scale,scale={0}:{1}:fast_bilinear";
 3280                }
 3281            }
 3282
 03283            return string.Format(
 03284                CultureInfo.InvariantCulture,
 03285                filters,
 03286                outWidth.Value,
 03287                outHeight.Value);
 3288        }
 3289
 3290        public static string GetAlphaSrcFilter(
 3291            EncodingJobInfo state,
 3292            int? videoWidth,
 3293            int? videoHeight,
 3294            int? requestedWidth,
 3295            int? requestedHeight,
 3296            int? requestedMaxWidth,
 3297            int? requestedMaxHeight,
 3298            float? framerate)
 3299        {
 03300            var reqTicks = state.BaseRequest.StartTimeTicks ?? 0;
 03301            var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture
 03302            var (outWidth, outHeight) = GetFixedOutputSize(
 03303                videoWidth,
 03304                videoHeight,
 03305                requestedWidth,
 03306                requestedHeight,
 03307                requestedMaxWidth,
 03308                requestedMaxHeight);
 3309
 03310            if (outWidth.HasValue && outHeight.HasValue)
 3311            {
 03312                return string.Format(
 03313                    CultureInfo.InvariantCulture,
 03314                    "alphasrc=s={0}x{1}:r={2}:start='{3}'",
 03315                    outWidth.Value,
 03316                    outHeight.Value,
 03317                    framerate ?? 25,
 03318                    reqTicks > 0 ? startTime : 0);
 3319            }
 3320
 03321            return string.Empty;
 3322        }
 3323
 3324        public static string GetSwScaleFilter(
 3325            EncodingJobInfo state,
 3326            EncodingOptions options,
 3327            string videoEncoder,
 3328            int? videoWidth,
 3329            int? videoHeight,
 3330            Video3DFormat? threedFormat,
 3331            int? requestedWidth,
 3332            int? requestedHeight,
 3333            int? requestedMaxWidth,
 3334            int? requestedMaxHeight)
 3335        {
 03336            var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 03337            var isMjpeg = videoEncoder is not null && videoEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase)
 03338            var scaleVal = isV4l2 ? 64 : 2;
 03339            var targetAr = isMjpeg ? "(a*sar)" : "a"; // manually calculate AR when using mjpeg encoder
 3340
 3341            // If fixed dimensions were supplied
 03342            if (requestedWidth.HasValue && requestedHeight.HasValue)
 3343            {
 03344                if (isV4l2)
 3345                {
 03346                    var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 03347                    var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3348
 03349                    return string.Format(
 03350                            CultureInfo.InvariantCulture,
 03351                            "scale=trunc({0}/64)*64:trunc({1}/2)*2",
 03352                            widthParam,
 03353                            heightParam);
 3354                }
 3355
 03356                return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
 3357            }
 3358
 3359            // If Max dimensions were supplied, for width selects lowest even number between input width and width req s
 3360
 03361            if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
 3362            {
 03363                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 03364                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3365
 03366                return string.Format(
 03367                    CultureInfo.InvariantCulture,
 03368                    @"scale=trunc(min(max(iw\,ih*{3})\,min({0}\,{1}*{3}))/{2})*{2}:trunc(min(max(iw/{3}\,ih)\,min({0}/{3
 03369                    maxWidthParam,
 03370                    maxHeightParam,
 03371                    scaleVal,
 03372                    targetAr);
 3373            }
 3374
 3375            // If a fixed width was requested
 03376            if (requestedWidth.HasValue)
 3377            {
 03378                if (threedFormat.HasValue)
 3379                {
 3380                    // This method can handle 0 being passed in for the requested height
 03381                    return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0);
 3382                }
 3383
 03384                var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 3385
 03386                return string.Format(
 03387                    CultureInfo.InvariantCulture,
 03388                    "scale={0}:trunc(ow/{1}/2)*2",
 03389                    widthParam,
 03390                    targetAr);
 3391            }
 3392
 3393            // If a fixed height was requested
 03394            if (requestedHeight.HasValue)
 3395            {
 03396                var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3397
 03398                return string.Format(
 03399                    CultureInfo.InvariantCulture,
 03400                    "scale=trunc(oh*{2}/{1})*{1}:{0}",
 03401                    heightParam,
 03402                    scaleVal,
 03403                    targetAr);
 3404            }
 3405
 3406            // If a max width was requested
 03407            if (requestedMaxWidth.HasValue)
 3408            {
 03409                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 3410
 03411                return string.Format(
 03412                    CultureInfo.InvariantCulture,
 03413                    @"scale=trunc(min(max(iw\,ih*{2})\,{0})/{1})*{1}:trunc(ow/{2}/2)*2",
 03414                    maxWidthParam,
 03415                    scaleVal,
 03416                    targetAr);
 3417            }
 3418
 3419            // If a max height was requested
 03420            if (requestedMaxHeight.HasValue)
 3421            {
 03422                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3423
 03424                return string.Format(
 03425                    CultureInfo.InvariantCulture,
 03426                    @"scale=trunc(oh*{2}/{1})*{1}:min(max(iw/{2}\,ih)\,{0})",
 03427                    maxHeightParam,
 03428                    scaleVal,
 03429                    targetAr);
 3430            }
 3431
 03432            return string.Empty;
 3433        }
 3434
 3435        private static string GetFixedSwScaleFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight
 3436        {
 03437            var widthParam = requestedWidth.ToString(CultureInfo.InvariantCulture);
 03438            var heightParam = requestedHeight.ToString(CultureInfo.InvariantCulture);
 3439
 03440            string filter = null;
 3441
 03442            if (threedFormat.HasValue)
 3443            {
 03444                switch (threedFormat.Value)
 3445                {
 3446                    case Video3DFormat.HalfSideBySide:
 03447                        filter = @"crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(i
 3448                        // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars
 03449                        break;
 3450                    case Video3DFormat.FullSideBySide:
 03451                        filter = @"crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3452                        // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the s
 03453                        break;
 3454                    case Video3DFormat.HalfTopAndBottom:
 03455                        filter = @"crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(
 3456                        // htab crop height in half,scale to correct size, set the display aspect,crop out any black bar
 03457                        break;
 3458                    case Video3DFormat.FullTopAndBottom:
 03459                        filter = @"crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3460                        // ftab crop height in half, set the display aspect,crop out any black bars we may have made the
 3461                        break;
 3462                    default:
 3463                        break;
 3464                }
 3465            }
 3466
 3467            // default
 03468            if (filter is null)
 3469            {
 03470                if (requestedHeight > 0)
 3471                {
 03472                    filter = "scale=trunc({0}/2)*2:trunc({1}/2)*2";
 3473                }
 3474                else
 3475                {
 03476                    filter = "scale={0}:trunc({0}/a/2)*2";
 3477                }
 3478            }
 3479
 03480            return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam);
 3481        }
 3482
 3483        public static string GetSwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options)
 3484        {
 03485            var doubleRateDeint = options.DeinterlaceDoubleRate && state.VideoStream?.ReferenceFrameRate <= 30;
 03486            return string.Format(
 03487                CultureInfo.InvariantCulture,
 03488                "{0}={1}:-1:0",
 03489                options.DeinterlaceMethod.ToString().ToLowerInvariant(),
 03490                doubleRateDeint ? "1" : "0");
 3491        }
 3492
 3493        public string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix)
 3494        {
 03495            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03496            if (hwDeintSuffix.Contains("cuda", StringComparison.OrdinalIgnoreCase))
 3497            {
 03498                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3499
 03500                return string.Format(
 03501                    CultureInfo.InvariantCulture,
 03502                    "{0}_cuda={1}:-1:0",
 03503                    useBwdif ? "bwdif" : "yadif",
 03504                    doubleRateDeint ? "1" : "0");
 3505            }
 3506
 03507            if (hwDeintSuffix.Contains("opencl", StringComparison.OrdinalIgnoreCase))
 3508            {
 03509                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif;
 3510
 03511                if (_mediaEncoder.SupportsFilter("yadif_opencl")
 03512                    && _mediaEncoder.SupportsFilter("bwdif_opencl"))
 3513                {
 03514                    return string.Format(
 03515                        CultureInfo.InvariantCulture,
 03516                        "{0}_opencl={1}:-1:0",
 03517                        useBwdif ? "bwdif" : "yadif",
 03518                        doubleRateDeint ? "1" : "0");
 3519                }
 3520            }
 3521
 03522            if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
 3523            {
 03524                return string.Format(
 03525                    CultureInfo.InvariantCulture,
 03526                    "deinterlace_vaapi=rate={0}",
 03527                    doubleRateDeint ? "field" : "frame");
 3528            }
 3529
 03530            if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
 3531            {
 03532                return "deinterlace_qsv=mode=2";
 3533            }
 3534
 03535            if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
 3536            {
 03537                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3538
 03539                return string.Format(
 03540                    CultureInfo.InvariantCulture,
 03541                    "{0}_videotoolbox={1}:-1:0",
 03542                    useBwdif ? "bwdif" : "yadif",
 03543                    doubleRateDeint ? "1" : "0");
 3544            }
 3545
 03546            return string.Empty;
 3547        }
 3548
 3549        private string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat, bool forc
 3550        {
 03551            if (string.IsNullOrEmpty(hwTonemapSuffix))
 3552            {
 03553                return string.Empty;
 3554            }
 3555
 03556            var args = string.Empty;
 03557            var algorithm = options.TonemappingAlgorithm.ToString().ToLowerInvariant();
 03558            var mode = options.TonemappingMode.ToString().ToLowerInvariant();
 03559            var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 03560            var rangeString = range.ToString().ToLowerInvariant();
 3561
 03562            if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
 3563            {
 03564                var doVaVppProcamp = false;
 03565                var procampParams = string.Empty;
 03566                if (options.VppTonemappingBrightness != 0
 03567                    && options.VppTonemappingBrightness >= -100
 03568                    && options.VppTonemappingBrightness <= 100)
 3569                {
 03570                    procampParams += "procamp_vaapi=b={0}";
 03571                    doVaVppProcamp = true;
 3572                }
 3573
 03574                if (options.VppTonemappingContrast > 1
 03575                    && options.VppTonemappingContrast <= 10)
 3576                {
 03577                    procampParams += doVaVppProcamp ? ":c={1}" : "procamp_vaapi=c={1}";
 03578                    doVaVppProcamp = true;
 3579                }
 3580
 03581                args = procampParams + "{2}tonemap_vaapi=format={3}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
 3582
 03583                return string.Format(
 03584                        CultureInfo.InvariantCulture,
 03585                        args,
 03586                        options.VppTonemappingBrightness,
 03587                        options.VppTonemappingContrast,
 03588                        doVaVppProcamp ? "," : string.Empty,
 03589                        videoFormat ?? "nv12");
 3590            }
 3591            else
 3592            {
 03593                args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
 3594
 03595                var useLegacyTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode
 03596                                           && _legacyTonemapModes.Contains(options.TonemappingMode);
 3597
 03598                var useAdvancedTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegAdvancedTonemapMode
 03599                                              && _advancedTonemapModes.Contains(options.TonemappingMode);
 3600
 03601                if (useLegacyTonemapModes || useAdvancedTonemapModes)
 3602                {
 03603                    args += ":tonemap_mode={5}";
 3604                }
 3605
 03606                if (options.TonemappingParam != 0)
 3607                {
 03608                    args += ":param={6}";
 3609                }
 3610
 03611                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3612                {
 03613                    args += ":range={7}";
 3614                }
 3615            }
 3616
 03617            return string.Format(
 03618                    CultureInfo.InvariantCulture,
 03619                    args,
 03620                    hwTonemapSuffix,
 03621                    videoFormat ?? "nv12",
 03622                    algorithm,
 03623                    options.TonemappingPeak,
 03624                    options.TonemappingDesat,
 03625                    mode,
 03626                    options.TonemappingParam,
 03627                    rangeString);
 3628        }
 3629
 3630        private string GetLibplaceboFilter(
 3631            EncodingOptions options,
 3632            string videoFormat,
 3633            bool doTonemap,
 3634            int? videoWidth,
 3635            int? videoHeight,
 3636            int? requestedWidth,
 3637            int? requestedHeight,
 3638            int? requestedMaxWidth,
 3639            int? requestedMaxHeight,
 3640            bool forceFullRange)
 3641        {
 03642            var (outWidth, outHeight) = GetFixedOutputSize(
 03643                videoWidth,
 03644                videoHeight,
 03645                requestedWidth,
 03646                requestedHeight,
 03647                requestedMaxWidth,
 03648                requestedMaxHeight);
 3649
 03650            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03651            var isSizeFixed = !videoWidth.HasValue
 03652                || outWidth.Value != videoWidth.Value
 03653                || !videoHeight.HasValue
 03654                || outHeight.Value != videoHeight.Value;
 3655
 03656            var sizeArg = isSizeFixed ? (":w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty;
 03657            var formatArg = isFormatFixed ? (":format=" + videoFormat) : string.Empty;
 03658            var tonemapArg = string.Empty;
 3659
 03660            if (doTonemap)
 3661            {
 03662                var algorithm = options.TonemappingAlgorithm;
 03663                var algorithmString = "clip";
 03664                var mode = options.TonemappingMode;
 03665                var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 3666
 03667                if (algorithm == TonemappingAlgorithm.bt2390)
 3668                {
 03669                    algorithmString = "bt.2390";
 3670                }
 03671                else if (algorithm != TonemappingAlgorithm.none)
 3672                {
 03673                    algorithmString = algorithm.ToString().ToLowerInvariant();
 3674                }
 3675
 03676                tonemapArg = $":tonemapping={algorithmString}:peak_detect=0:color_primaries=bt709:color_trc=bt709:colors
 3677
 03678                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3679                {
 03680                    tonemapArg += ":range=" + range.ToString().ToLowerInvariant();
 3681                }
 3682            }
 3683
 03684            return string.Format(
 03685                CultureInfo.InvariantCulture,
 03686                "libplacebo=upscaler=none:downscaler=none{0}{1}{2}",
 03687                sizeArg,
 03688                formatArg,
 03689                tonemapArg);
 3690        }
 3691
 3692        public string GetVideoTransposeDirection(EncodingJobInfo state)
 3693        {
 03694            return (state.VideoStream?.Rotation ?? 0) switch
 03695            {
 03696                90 => "cclock",
 03697                180 => "reversal",
 03698                -90 => "clock",
 03699                -180 => "reversal",
 03700                _ => string.Empty
 03701            };
 3702        }
 3703
 3704        /// <summary>
 3705        /// Gets the parameter of software filter chain.
 3706        /// </summary>
 3707        /// <param name="state">Encoding state.</param>
 3708        /// <param name="options">Encoding options.</param>
 3709        /// <param name="vidEncoder">Video encoder to use.</param>
 3710        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3711        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetSwVidFilterChain(
 3712            EncodingJobInfo state,
 3713            EncodingOptions options,
 3714            string vidEncoder)
 3715        {
 03716            var inW = state.VideoStream?.Width;
 03717            var inH = state.VideoStream?.Height;
 03718            var reqW = state.BaseRequest.Width;
 03719            var reqH = state.BaseRequest.Height;
 03720            var reqMaxW = state.BaseRequest.MaxWidth;
 03721            var reqMaxH = state.BaseRequest.MaxHeight;
 03722            var threeDFormat = state.MediaSource.Video3DFormat;
 3723
 03724            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03725            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03726            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 03727            var isV4l2Encoder = vidEncoder.Contains("h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 3728
 03729            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03730            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03731            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03732            var doToneMap = IsSwTonemapAvailable(state, options);
 03733            var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI;
 3734
 03735            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03736            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03737            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 3738
 03739            var rotation = state.VideoStream?.Rotation ?? 0;
 03740            var swapWAndH = Math.Abs(rotation) == 90;
 03741            var swpInW = swapWAndH ? inH : inW;
 03742            var swpInH = swapWAndH ? inW : inH;
 3743
 3744            /* Make main filters for video stream */
 03745            var mainFilters = new List<string>();
 3746
 03747            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doToneMap));
 3748
 3749            // INPUT sw surface(memory/copy-back from vram)
 3750            // sw deint
 03751            if (doDeintH2645)
 3752            {
 03753                var deintFilter = GetSwDeinterlaceFilter(state, options);
 03754                mainFilters.Add(deintFilter);
 3755            }
 3756
 03757            var outFormat = isSwDecoder ? "yuv420p" : "nv12";
 03758            var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, r
 03759            if (isVaapiEncoder)
 3760            {
 03761                outFormat = "nv12";
 3762            }
 03763            else if (isV4l2Encoder)
 3764            {
 03765                outFormat = "yuv420p";
 3766            }
 3767
 3768            // sw scale
 03769            mainFilters.Add(swScaleFilter);
 3770
 3771            // sw tonemap
 03772            if (doToneMap)
 3773            {
 3774                // tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary
 03775                var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat;
 03776                var tonemapArgString = "tonemapx=tonemap={0}:desat={1}:peak={2}:t=bt709:m=bt709:p=bt709:format={3}";
 3777
 03778                if (options.TonemappingParam != 0)
 3779                {
 03780                    tonemapArgString += ":param={4}";
 3781                }
 3782
 03783                var range = options.TonemappingRange;
 03784                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3785                {
 03786                    tonemapArgString += ":range={5}";
 3787                }
 3788
 03789                var tonemapArgs = string.Format(
 03790                    CultureInfo.InvariantCulture,
 03791                    tonemapArgString,
 03792                    options.TonemappingAlgorithm,
 03793                    options.TonemappingDesat,
 03794                    options.TonemappingPeak,
 03795                    tonemapFormat,
 03796                    options.TonemappingParam,
 03797                    options.TonemappingRange);
 3798
 03799                mainFilters.Add(tonemapArgs);
 3800            }
 3801            else
 3802            {
 3803                // OUTPUT yuv420p/nv12 surface(memory)
 03804                mainFilters.Add("format=" + outFormat);
 3805            }
 3806
 3807            /* Make sub and overlay filters for subtitle stream */
 03808            var subFilters = new List<string>();
 03809            var overlayFilters = new List<string>();
 03810            if (hasTextSubs)
 3811            {
 3812                // subtitles=f='*.ass':alpha=0
 03813                var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03814                mainFilters.Add(textSubtitlesFilter);
 3815            }
 03816            else if (hasGraphicalSubs)
 3817            {
 03818                var subW = state.SubtitleStream?.Width;
 03819                var subH = state.SubtitleStream?.Height;
 03820                var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW
 03821                subFilters.Add(subPreProcFilters);
 03822                overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 3823            }
 3824
 03825            return (mainFilters, subFilters, overlayFilters);
 3826        }
 3827
 3828        /// <summary>
 3829        /// Gets the parameter of Nvidia NVENC filter chain.
 3830        /// </summary>
 3831        /// <param name="state">Encoding state.</param>
 3832        /// <param name="options">Encoding options.</param>
 3833        /// <param name="vidEncoder">Video encoder to use.</param>
 3834        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3835        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFilterChain(
 3836            EncodingJobInfo state,
 3837            EncodingOptions options,
 3838            string vidEncoder)
 3839        {
 03840            if (options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 3841            {
 03842                return (null, null, null);
 3843            }
 3844
 03845            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03846            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03847            var isSwEncoder = !vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 3848
 3849            // legacy cuvid pipeline(copy-back)
 03850            if ((isSwDecoder && isSwEncoder)
 03851                || !IsCudaFullSupported()
 03852                || !_mediaEncoder.SupportsFilter("alphasrc"))
 3853            {
 03854                return GetSwVidFilterChain(state, options, vidEncoder);
 3855            }
 3856
 3857            // preferred nvdec/cuvid + cuda filters + nvenc pipeline
 03858            return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 3859        }
 3860
 3861        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFiltersPrefe
 3862            EncodingJobInfo state,
 3863            EncodingOptions options,
 3864            string vidDecoder,
 3865            string vidEncoder)
 3866        {
 03867            var inW = state.VideoStream?.Width;
 03868            var inH = state.VideoStream?.Height;
 03869            var reqW = state.BaseRequest.Width;
 03870            var reqH = state.BaseRequest.Height;
 03871            var reqMaxW = state.BaseRequest.MaxWidth;
 03872            var reqMaxH = state.BaseRequest.MaxHeight;
 03873            var threeDFormat = state.MediaSource.Video3DFormat;
 3874
 03875            var isNvDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 03876            var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 03877            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03878            var isSwEncoder = !isNvencEncoder;
 03879            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 03880            var isCuInCuOut = isNvDecoder && isNvencEncoder;
 3881
 03882            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03883            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03884            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03885            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03886            var doCuTonemap = IsHwTonemapAvailable(state, options);
 3887
 03888            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03889            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03890            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 03891            var hasAssSubs = hasSubs
 03892                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 03893                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 03894            var subW = state.SubtitleStream?.Width;
 03895            var subH = state.SubtitleStream?.Height;
 3896
 03897            var rotation = state.VideoStream?.Rotation ?? 0;
 03898            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 03899            var doCuTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_cuda");
 03900            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isNvDecoder && doCuTranspose));
 03901            var swpInW = swapWAndH ? inH : inW;
 03902            var swpInH = swapWAndH ? inW : inH;
 3903
 3904            /* Make main filters for video stream */
 03905            var mainFilters = new List<string>();
 3906
 03907            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doCuTonemap));
 3908
 03909            if (isSwDecoder)
 3910            {
 3911                // INPUT sw surface(memory)
 3912                // sw deint
 03913                if (doDeintH2645)
 3914                {
 03915                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 03916                    mainFilters.Add(swDeintFilter);
 3917                }
 3918
 03919                var outFormat = doCuTonemap ? "yuv420p10le" : "yuv420p";
 03920                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 3921                // sw scale
 03922                mainFilters.Add(swScaleFilter);
 03923                mainFilters.Add($"format={outFormat}");
 3924
 3925                // sw => hw
 03926                if (doCuTonemap)
 3927                {
 03928                    mainFilters.Add("hwupload=derive_device=cuda");
 3929                }
 3930            }
 3931
 03932            if (isNvDecoder)
 3933            {
 3934                // INPUT cuda surface(vram)
 3935                // hw deint
 03936                if (doDeintH2645)
 3937                {
 03938                    var deintFilter = GetHwDeinterlaceFilter(state, options, "cuda");
 03939                    mainFilters.Add(deintFilter);
 3940                }
 3941
 3942                // hw transpose
 03943                if (doCuTranspose)
 3944                {
 03945                    mainFilters.Add($"transpose_cuda=dir={transposeDir}");
 3946                }
 3947
 03948                var isRext = IsVideoStreamHevcRext(state);
 03949                var outFormat = doCuTonemap ? (isRext ? "p010" : string.Empty) : "yuv420p";
 03950                var hwScaleFilter = GetHwScaleFilter("scale", "cuda", outFormat, false, swpInW, swpInH, reqW, reqH, reqM
 3951                // hw scale
 03952                mainFilters.Add(hwScaleFilter);
 3953            }
 3954
 3955            // hw tonemap
 03956            if (doCuTonemap)
 3957            {
 03958                var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p", isMjpegEncoder);
 03959                mainFilters.Add(tonemapFilter);
 3960            }
 3961
 03962            var memoryOutput = false;
 03963            var isUploadForCuTonemap = isSwDecoder && doCuTonemap;
 03964            if ((isNvDecoder && isSwEncoder) || (isUploadForCuTonemap && hasSubs))
 3965            {
 03966                memoryOutput = true;
 3967
 3968                // OUTPUT yuv420p surface(memory)
 03969                mainFilters.Add("hwdownload");
 03970                mainFilters.Add("format=yuv420p");
 3971            }
 3972
 3973            // OUTPUT yuv420p surface(memory)
 03974            if (isSwDecoder && isNvencEncoder && !isUploadForCuTonemap)
 3975            {
 03976                memoryOutput = true;
 3977            }
 3978
 03979            if (memoryOutput)
 3980            {
 3981                // text subtitles
 03982                if (hasTextSubs)
 3983                {
 03984                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03985                    mainFilters.Add(textSubtitlesFilter);
 3986                }
 3987            }
 3988
 3989            // OUTPUT cuda(yuv420p) surface(vram)
 3990
 3991            /* Make sub and overlay filters for subtitle stream */
 03992            var subFilters = new List<string>();
 03993            var overlayFilters = new List<string>();
 03994            if (isCuInCuOut)
 3995            {
 03996                if (hasSubs)
 3997                {
 03998                    var alphaFormatOpt = string.Empty;
 03999                    if (hasGraphicalSubs)
 4000                    {
 04001                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04002                        subFilters.Add(subPreProcFilters);
 04003                        subFilters.Add("format=yuva420p");
 4004                    }
 04005                    else if (hasTextSubs)
 4006                    {
 04007                        var framerate = state.VideoStream?.RealFrameRate;
 04008                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4009
 4010                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04011                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04012                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04013                        subFilters.Add(alphaSrcFilter);
 04014                        subFilters.Add("format=yuva420p");
 04015                        subFilters.Add(subTextSubtitlesFilter);
 4016
 04017                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayCudaAlphaFormat)
 04018                            ? ":alpha_format=premultiplied" : string.Empty;
 4019                    }
 4020
 04021                    subFilters.Add("hwupload=derive_device=cuda");
 04022                    overlayFilters.Add($"overlay_cuda=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 4023                }
 4024            }
 4025            else
 4026            {
 04027                if (hasGraphicalSubs)
 4028                {
 04029                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04030                    subFilters.Add(subPreProcFilters);
 04031                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4032                }
 4033            }
 4034
 04035            return (mainFilters, subFilters, overlayFilters);
 4036        }
 4037
 4038        /// <summary>
 4039        /// Gets the parameter of AMD AMF filter chain.
 4040        /// </summary>
 4041        /// <param name="state">Encoding state.</param>
 4042        /// <param name="options">Encoding options.</param>
 4043        /// <param name="vidEncoder">Video encoder to use.</param>
 4044        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4045        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVidFilterChain(
 4046            EncodingJobInfo state,
 4047            EncodingOptions options,
 4048            string vidEncoder)
 4049        {
 04050            if (options.HardwareAccelerationType != HardwareAccelerationType.amf)
 4051            {
 04052                return (null, null, null);
 4053            }
 4054
 04055            var isWindows = OperatingSystem.IsWindows();
 04056            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04057            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04058            var isSwEncoder = !vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04059            var isAmfDx11OclSupported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va") && IsOpenclFullSupported()
 4060
 4061            // legacy d3d11va pipeline(copy-back)
 04062            if ((isSwDecoder && isSwEncoder)
 04063                || !isAmfDx11OclSupported
 04064                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4065            {
 04066                return GetSwVidFilterChain(state, options, vidEncoder);
 4067            }
 4068
 4069            // preferred d3d11va + opencl filters + amf pipeline
 04070            return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4071        }
 4072
 4073        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdDx11VidFiltersPref
 4074            EncodingJobInfo state,
 4075            EncodingOptions options,
 4076            string vidDecoder,
 4077            string vidEncoder)
 4078        {
 04079            var inW = state.VideoStream?.Width;
 04080            var inH = state.VideoStream?.Height;
 04081            var reqW = state.BaseRequest.Width;
 04082            var reqH = state.BaseRequest.Height;
 04083            var reqMaxW = state.BaseRequest.MaxWidth;
 04084            var reqMaxH = state.BaseRequest.MaxHeight;
 04085            var threeDFormat = state.MediaSource.Video3DFormat;
 4086
 04087            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04088            var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04089            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04090            var isSwEncoder = !isAmfEncoder;
 04091            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04092            var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder;
 4093
 04094            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04095            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04096            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04097            var doOclTonemap = IsHwTonemapAvailable(state, options);
 4098
 04099            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04100            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04101            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04102            var hasAssSubs = hasSubs
 04103                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04104                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04105            var subW = state.SubtitleStream?.Width;
 04106            var subH = state.SubtitleStream?.Height;
 4107
 04108            var rotation = state.VideoStream?.Rotation ?? 0;
 04109            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04110            var doOclTranspose = !string.IsNullOrEmpty(transposeDir)
 04111                && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TransposeOpenclReversal);
 04112            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isD3d11vaDecoder && doOclTranspose));
 04113            var swpInW = swapWAndH ? inH : inW;
 04114            var swpInH = swapWAndH ? inW : inH;
 4115
 4116            /* Make main filters for video stream */
 04117            var mainFilters = new List<string>();
 4118
 04119            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 4120
 04121            if (isSwDecoder)
 4122            {
 4123                // INPUT sw surface(memory)
 4124                // sw deint
 04125                if (doDeintH2645)
 4126                {
 04127                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04128                    mainFilters.Add(swDeintFilter);
 4129                }
 4130
 04131                var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
 04132                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 4133                // sw scale
 04134                mainFilters.Add(swScaleFilter);
 04135                mainFilters.Add($"format={outFormat}");
 4136
 4137                // keep video at memory except ocl tonemap,
 4138                // since the overhead caused by hwupload >>> using sw filter.
 4139                // sw => hw
 04140                if (doOclTonemap)
 4141                {
 04142                    mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=24");
 04143                    mainFilters.Add("format=d3d11");
 04144                    mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4145                }
 4146            }
 4147
 04148            if (isD3d11vaDecoder)
 4149            {
 4150                // INPUT d3d11 surface(vram)
 4151                // map from d3d11va to opencl via d3d11-opencl interop.
 04152                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4153
 4154                // hw deint
 04155                if (doDeintH2645)
 4156                {
 04157                    var deintFilter = GetHwDeinterlaceFilter(state, options, "opencl");
 04158                    mainFilters.Add(deintFilter);
 4159                }
 4160
 4161                // hw transpose
 04162                if (doOclTranspose)
 4163                {
 04164                    mainFilters.Add($"transpose_opencl=dir={transposeDir}");
 4165                }
 4166
 04167                var outFormat = doOclTonemap ? string.Empty : "nv12";
 04168                var hwScaleFilter = GetHwScaleFilter("scale", "opencl", outFormat, false, swpInW, swpInH, reqW, reqH, re
 4169                // hw scale
 04170                mainFilters.Add(hwScaleFilter);
 4171            }
 4172
 4173            // hw tonemap
 04174            if (doOclTonemap)
 4175            {
 04176                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04177                mainFilters.Add(tonemapFilter);
 4178            }
 4179
 04180            var memoryOutput = false;
 04181            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04182            if (isD3d11vaDecoder && isSwEncoder)
 4183            {
 04184                memoryOutput = true;
 4185
 4186                // OUTPUT nv12 surface(memory)
 4187                // prefer hwmap to hwdownload on opencl.
 04188                var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap=mode=read";
 04189                mainFilters.Add(hwTransferFilter);
 04190                mainFilters.Add("format=nv12");
 4191            }
 4192
 4193            // OUTPUT yuv420p surface
 04194            if (isSwDecoder && isAmfEncoder && !isUploadForOclTonemap)
 4195            {
 04196                memoryOutput = true;
 4197            }
 4198
 04199            if (memoryOutput)
 4200            {
 4201                // text subtitles
 04202                if (hasTextSubs)
 4203                {
 04204                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04205                    mainFilters.Add(textSubtitlesFilter);
 4206                }
 4207            }
 4208
 04209            if ((isDxInDxOut || isUploadForOclTonemap) && !hasSubs)
 4210            {
 4211                // OUTPUT d3d11(nv12) surface(vram)
 4212                // reverse-mapping via d3d11-opencl interop.
 04213                mainFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04214                mainFilters.Add("format=d3d11");
 4215            }
 4216
 4217            /* Make sub and overlay filters for subtitle stream */
 04218            var subFilters = new List<string>();
 04219            var overlayFilters = new List<string>();
 04220            if (isDxInDxOut || isUploadForOclTonemap)
 4221            {
 04222                if (hasSubs)
 4223                {
 04224                    var alphaFormatOpt = string.Empty;
 04225                    if (hasGraphicalSubs)
 4226                    {
 04227                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04228                        subFilters.Add(subPreProcFilters);
 04229                        subFilters.Add("format=yuva420p");
 4230                    }
 04231                    else if (hasTextSubs)
 4232                    {
 04233                        var framerate = state.VideoStream?.RealFrameRate;
 04234                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4235
 4236                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04237                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04238                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04239                        subFilters.Add(alphaSrcFilter);
 04240                        subFilters.Add("format=yuva420p");
 04241                        subFilters.Add(subTextSubtitlesFilter);
 4242
 04243                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclAlphaForma
 04244                            ? ":alpha_format=premultiplied" : string.Empty;
 4245                    }
 4246
 04247                    subFilters.Add("hwupload=derive_device=opencl");
 04248                    overlayFilters.Add($"overlay_opencl=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 04249                    overlayFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04250                    overlayFilters.Add("format=d3d11");
 4251                }
 4252            }
 04253            else if (memoryOutput)
 4254            {
 04255                if (hasGraphicalSubs)
 4256                {
 04257                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04258                    subFilters.Add(subPreProcFilters);
 04259                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4260                }
 4261            }
 4262
 04263            return (mainFilters, subFilters, overlayFilters);
 4264        }
 4265
 4266        /// <summary>
 4267        /// Gets the parameter of Intel QSV filter chain.
 4268        /// </summary>
 4269        /// <param name="state">Encoding state.</param>
 4270        /// <param name="options">Encoding options.</param>
 4271        /// <param name="vidEncoder">Video encoder to use.</param>
 4272        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4273        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVidFilterChain(
 4274            EncodingJobInfo state,
 4275            EncodingOptions options,
 4276            string vidEncoder)
 4277        {
 04278            if (options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 4279            {
 04280                return (null, null, null);
 4281            }
 4282
 04283            var isWindows = OperatingSystem.IsWindows();
 04284            var isLinux = OperatingSystem.IsLinux();
 04285            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04286            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04287            var isSwEncoder = !vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04288            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 04289            var isIntelDx11OclSupported = isWindows
 04290                && _mediaEncoder.SupportsHwaccel("d3d11va")
 04291                && isQsvOclSupported;
 04292            var isIntelVaapiOclSupported = isLinux
 04293                && IsVaapiSupported(state)
 04294                && isQsvOclSupported;
 4295
 4296            // legacy qsv pipeline(copy-back)
 04297            if ((isSwDecoder && isSwEncoder)
 04298                || (!isIntelVaapiOclSupported && !isIntelDx11OclSupported)
 04299                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4300            {
 04301                return GetSwVidFilterChain(state, options, vidEncoder);
 4302            }
 4303
 4304            // preferred qsv(vaapi) + opencl filters pipeline
 04305            if (isIntelVaapiOclSupported)
 4306            {
 04307                return GetIntelQsvVaapiVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4308            }
 4309
 4310            // preferred qsv(d3d11) + opencl filters pipeline
 04311            if (isIntelDx11OclSupported)
 4312            {
 04313                return GetIntelQsvDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4314            }
 4315
 04316            return (null, null, null);
 4317        }
 4318
 4319        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvDx11VidFilter
 4320            EncodingJobInfo state,
 4321            EncodingOptions options,
 4322            string vidDecoder,
 4323            string vidEncoder)
 4324        {
 04325            var inW = state.VideoStream?.Width;
 04326            var inH = state.VideoStream?.Height;
 04327            var reqW = state.BaseRequest.Width;
 04328            var reqH = state.BaseRequest.Height;
 04329            var reqMaxW = state.BaseRequest.MaxWidth;
 04330            var reqMaxH = state.BaseRequest.MaxHeight;
 04331            var threeDFormat = state.MediaSource.Video3DFormat;
 4332
 04333            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04334            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04335            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04336            var isHwDecoder = isD3d11vaDecoder || isQsvDecoder;
 04337            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04338            var isSwEncoder = !isQsvEncoder;
 04339            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04340            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4341
 04342            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04343            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04344            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04345            var doVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04346            var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options);
 04347            var doTonemap = doVppTonemap || doOclTonemap;
 4348
 04349            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04350            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04351            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04352            var hasAssSubs = hasSubs
 04353                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04354                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04355            var subW = state.SubtitleStream?.Width;
 04356            var subH = state.SubtitleStream?.Height;
 4357
 04358            var rotation = state.VideoStream?.Rotation ?? 0;
 04359            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04360            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04361            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isD3d11vaDecoder || isQsvDecoder) && doVppTran
 04362            var swpInW = swapWAndH ? inH : inW;
 04363            var swpInH = swapWAndH ? inW : inH;
 4364
 4365            /* Make main filters for video stream */
 04366            var mainFilters = new List<string>();
 4367
 04368            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4369
 04370            if (isSwDecoder)
 4371            {
 4372                // INPUT sw surface(memory)
 4373                // sw deint
 04374                if (doDeintH2645)
 4375                {
 04376                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04377                    mainFilters.Add(swDeintFilter);
 4378                }
 4379
 04380                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04381                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04382                if (isMjpegEncoder && !doOclTonemap)
 4383                {
 4384                    // sw decoder + hw mjpeg encoder
 04385                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4386                }
 4387
 4388                // sw scale
 04389                mainFilters.Add(swScaleFilter);
 04390                mainFilters.Add($"format={outFormat}");
 4391
 4392                // keep video at memory except ocl tonemap,
 4393                // since the overhead caused by hwupload >>> using sw filter.
 4394                // sw => hw
 04395                if (doOclTonemap)
 4396                {
 04397                    mainFilters.Add("hwupload=derive_device=opencl");
 4398                }
 4399            }
 04400            else if (isD3d11vaDecoder || isQsvDecoder)
 4401            {
 04402                var isRext = IsVideoStreamHevcRext(state);
 04403                var twoPassVppTonemap = false;
 04404                var doVppFullRangeOut = isMjpegEncoder
 04405                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04406                var doVppScaleModeHq = isMjpegEncoder
 04407                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 04408                var doVppProcamp = false;
 04409                var procampParams = string.Empty;
 04410                var procampParamsString = string.Empty;
 04411                if (doVppTonemap)
 4412                {
 04413                    if (isRext)
 4414                    {
 4415                        // VPP tonemap requires p010 input
 04416                        twoPassVppTonemap = true;
 4417                    }
 4418
 04419                    if (options.VppTonemappingBrightness != 0
 04420                        && options.VppTonemappingBrightness >= -100
 04421                        && options.VppTonemappingBrightness <= 100)
 4422                    {
 04423                        procampParamsString += ":brightness={0}";
 04424                        twoPassVppTonemap = doVppProcamp = true;
 4425                    }
 4426
 04427                    if (options.VppTonemappingContrast > 1
 04428                        && options.VppTonemappingContrast <= 10)
 4429                    {
 04430                        procampParamsString += ":contrast={1}";
 04431                        twoPassVppTonemap = doVppProcamp = true;
 4432                    }
 4433
 04434                    if (doVppProcamp)
 4435                    {
 04436                        procampParamsString += ":procamp=1:async_depth=2";
 04437                        procampParams = string.Format(
 04438                            CultureInfo.InvariantCulture,
 04439                            procampParamsString,
 04440                            options.VppTonemappingBrightness,
 04441                            options.VppTonemappingContrast);
 4442                    }
 4443                }
 4444
 04445                var outFormat = doOclTonemap ? ((doVppTranspose || isRext) ? "p010" : string.Empty) : "nv12";
 04446                outFormat = twoPassVppTonemap ? "p010" : outFormat;
 4447
 04448                var swapOutputWandH = doVppTranspose && swapWAndH;
 04449                var hwScaleFilter = GetHwScaleFilter("vpp", "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, req
 4450
 4451                // d3d11va doesn't support dynamic pool size, use vpp filter ctx to relay
 4452                // to prevent encoder async and bframes from exhausting the decoder pool.
 04453                if (!string.IsNullOrEmpty(hwScaleFilter) && isD3d11vaDecoder)
 4454                {
 04455                    hwScaleFilter += ":passthrough=0";
 4456                }
 4457
 04458                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose)
 4459                {
 04460                    hwScaleFilter += $":transpose={transposeDir}";
 4461                }
 4462
 04463                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4464                {
 04465                    hwScaleFilter += (doVppFullRangeOut && !doOclTonemap) ? ":out_range=pc" : string.Empty;
 04466                    hwScaleFilter += doVppScaleModeHq ? ":scale_mode=hq" : string.Empty;
 4467                }
 4468
 04469                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTonemap)
 4470                {
 04471                    hwScaleFilter += doVppProcamp ? procampParams : (twoPassVppTonemap ? string.Empty : ":tonemap=1");
 4472                }
 4473
 04474                if (isD3d11vaDecoder)
 4475                {
 04476                    if (!string.IsNullOrEmpty(hwScaleFilter) || doDeintH2645)
 4477                    {
 4478                        // INPUT d3d11 surface(vram)
 4479                        // map from d3d11va to qsv.
 04480                        mainFilters.Add("hwmap=derive_device=qsv");
 4481                    }
 4482                }
 4483
 4484                // hw deint
 04485                if (doDeintH2645)
 4486                {
 04487                    var deintFilter = GetHwDeinterlaceFilter(state, options, "qsv");
 04488                    mainFilters.Add(deintFilter);
 4489                }
 4490
 4491                // hw transpose & scale & tonemap(w/o procamp)
 04492                mainFilters.Add(hwScaleFilter);
 4493
 4494                // hw tonemap(w/ procamp)
 04495                if (doVppTonemap && twoPassVppTonemap)
 4496                {
 04497                    mainFilters.Add("vpp_qsv=tonemap=1:format=nv12:async_depth=2");
 4498                }
 4499
 4500                // force bt709 just in case vpp tonemap is not triggered or using MSDK instead of VPL.
 04501                if (doVppTonemap)
 4502                {
 04503                    mainFilters.Add(GetOverwriteColorPropertiesParam(state, false));
 4504                }
 4505            }
 4506
 04507            if (doOclTonemap && isHwDecoder)
 4508            {
 4509                // map from qsv to opencl via qsv(d3d11)-opencl interop.
 04510                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4511            }
 4512
 4513            // hw tonemap
 04514            if (doOclTonemap)
 4515            {
 04516                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04517                mainFilters.Add(tonemapFilter);
 4518            }
 4519
 04520            var memoryOutput = false;
 04521            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04522            var isHwmapUsable = isSwEncoder && doOclTonemap;
 04523            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4524            {
 04525                memoryOutput = true;
 4526
 4527                // OUTPUT nv12 surface(memory)
 4528                // prefer hwmap to hwdownload on opencl.
 4529                // qsv hwmap is not fully implemented for the time being.
 04530                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04531                mainFilters.Add("format=nv12");
 4532            }
 4533
 4534            // OUTPUT nv12 surface(memory)
 04535            if (isSwDecoder && isQsvEncoder)
 4536            {
 04537                memoryOutput = true;
 4538            }
 4539
 04540            if (memoryOutput)
 4541            {
 4542                // text subtitles
 04543                if (hasTextSubs)
 4544                {
 04545                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04546                    mainFilters.Add(textSubtitlesFilter);
 4547                }
 4548            }
 4549
 04550            if (isQsvInQsvOut && doOclTonemap)
 4551            {
 4552                // OUTPUT qsv(nv12) surface(vram)
 4553                // reverse-mapping via qsv(d3d11)-opencl interop.
 04554                mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1");
 04555                mainFilters.Add("format=qsv");
 4556            }
 4557
 4558            /* Make sub and overlay filters for subtitle stream */
 04559            var subFilters = new List<string>();
 04560            var overlayFilters = new List<string>();
 04561            if (isQsvInQsvOut)
 4562            {
 04563                if (hasSubs)
 4564                {
 04565                    if (hasGraphicalSubs)
 4566                    {
 4567                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04568                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04569                        subFilters.Add(subPreProcFilters);
 04570                        subFilters.Add("format=bgra");
 4571                    }
 04572                    else if (hasTextSubs)
 4573                    {
 04574                        var framerate = state.VideoStream?.RealFrameRate;
 04575                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4576
 4577                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 04578                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04579                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04580                        subFilters.Add(alphaSrcFilter);
 04581                        subFilters.Add("format=bgra");
 04582                        subFilters.Add(subTextSubtitlesFilter);
 4583                    }
 4584
 4585                    // qsv requires a fixed pool size.
 4586                    // default to 64 otherwise it will fail on certain iGPU.
 04587                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4588
 04589                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04590                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04591                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04592                        : string.Empty;
 04593                    var overlayQsvFilter = string.Format(
 04594                        CultureInfo.InvariantCulture,
 04595                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04596                        overlaySize);
 04597                    overlayFilters.Add(overlayQsvFilter);
 4598                }
 4599            }
 04600            else if (memoryOutput)
 4601            {
 04602                if (hasGraphicalSubs)
 4603                {
 04604                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04605                    subFilters.Add(subPreProcFilters);
 04606                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4607                }
 4608            }
 4609
 04610            return (mainFilters, subFilters, overlayFilters);
 4611        }
 4612
 4613        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvVaapiVidFilte
 4614            EncodingJobInfo state,
 4615            EncodingOptions options,
 4616            string vidDecoder,
 4617            string vidEncoder)
 4618        {
 04619            var inW = state.VideoStream?.Width;
 04620            var inH = state.VideoStream?.Height;
 04621            var reqW = state.BaseRequest.Width;
 04622            var reqH = state.BaseRequest.Height;
 04623            var reqMaxW = state.BaseRequest.MaxWidth;
 04624            var reqMaxH = state.BaseRequest.MaxHeight;
 04625            var threeDFormat = state.MediaSource.Video3DFormat;
 4626
 04627            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04628            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04629            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04630            var isHwDecoder = isVaapiDecoder || isQsvDecoder;
 04631            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04632            var isSwEncoder = !isQsvEncoder;
 04633            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04634            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4635
 04636            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04637            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04638            var doVaVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04639            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04640            var doTonemap = doVaVppTonemap || doOclTonemap;
 04641            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4642
 04643            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04644            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04645            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04646            var hasAssSubs = hasSubs
 04647                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04648                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04649            var subW = state.SubtitleStream?.Width;
 04650            var subH = state.SubtitleStream?.Height;
 4651
 04652            var rotation = state.VideoStream?.Rotation ?? 0;
 04653            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04654            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04655            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isVaapiDecoder || isQsvDecoder) && doVppTransp
 04656            var swpInW = swapWAndH ? inH : inW;
 04657            var swpInH = swapWAndH ? inW : inH;
 4658
 4659            /* Make main filters for video stream */
 04660            var mainFilters = new List<string>();
 4661
 04662            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4663
 04664            if (isSwDecoder)
 4665            {
 4666                // INPUT sw surface(memory)
 4667                // sw deint
 04668                if (doDeintH2645)
 4669                {
 04670                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04671                    mainFilters.Add(swDeintFilter);
 4672                }
 4673
 04674                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04675                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04676                if (isMjpegEncoder && !doOclTonemap)
 4677                {
 4678                    // sw decoder + hw mjpeg encoder
 04679                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4680                }
 4681
 4682                // sw scale
 04683                mainFilters.Add(swScaleFilter);
 04684                mainFilters.Add($"format={outFormat}");
 4685
 4686                // keep video at memory except ocl tonemap,
 4687                // since the overhead caused by hwupload >>> using sw filter.
 4688                // sw => hw
 04689                if (doOclTonemap)
 4690                {
 04691                    mainFilters.Add("hwupload=derive_device=opencl");
 4692                }
 4693            }
 04694            else if (isVaapiDecoder || isQsvDecoder)
 4695            {
 04696                var hwFilterSuffix = isVaapiDecoder ? "vaapi" : "qsv";
 04697                var isRext = IsVideoStreamHevcRext(state);
 04698                var doVppFullRangeOut = isMjpegEncoder
 04699                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04700                var doVppScaleModeHq = isMjpegEncoder
 04701                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 4702
 4703                // INPUT vaapi/qsv surface(vram)
 4704                // hw deint
 04705                if (doDeintH2645)
 4706                {
 04707                    var deintFilter = GetHwDeinterlaceFilter(state, options, hwFilterSuffix);
 04708                    mainFilters.Add(deintFilter);
 4709                }
 4710
 4711                // hw transpose(vaapi vpp)
 04712                if (isVaapiDecoder && doVppTranspose)
 4713                {
 04714                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 4715                }
 4716
 04717                var outFormat = doTonemap ? (((isQsvDecoder && doVppTranspose) || isRext) ? "p010" : string.Empty) : "nv
 04718                var swapOutputWandH = isQsvDecoder && doVppTranspose && swapWAndH;
 04719                var hwScalePrefix = isQsvDecoder ? "vpp" : "scale";
 04720                var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, hwFilterSuffix, outFormat, swapOutputWandH, swpInW, 
 4721
 04722                if (!string.IsNullOrEmpty(hwScaleFilter) && isQsvDecoder && doVppTranspose)
 4723                {
 04724                    hwScaleFilter += $":transpose={transposeDir}";
 4725                }
 4726
 04727                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4728                {
 04729                    hwScaleFilter += ((isQsvDecoder && !doVppFullRangeOut) || doOclTonemap) ? string.Empty : ":out_range
 04730                    hwScaleFilter += isQsvDecoder ? (doVppScaleModeHq ? ":scale_mode=hq" : string.Empty) : ":mode=hq";
 4731                }
 4732
 4733                // allocate extra pool sizes for vaapi vpp scale
 04734                if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder)
 4735                {
 04736                    hwScaleFilter += ":extra_hw_frames=24";
 4737                }
 4738
 4739                // hw transpose(qsv vpp) & scale
 04740                mainFilters.Add(hwScaleFilter);
 4741            }
 4742
 4743            // vaapi vpp tonemap
 04744            if (doVaVppTonemap && isHwDecoder)
 4745            {
 04746                if (isQsvDecoder)
 4747                {
 4748                    // map from qsv to vaapi.
 04749                    mainFilters.Add("hwmap=derive_device=vaapi");
 04750                    mainFilters.Add("format=vaapi");
 4751                }
 4752
 04753                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 04754                mainFilters.Add(tonemapFilter);
 4755
 04756                if (isQsvDecoder)
 4757                {
 4758                    // map from vaapi to qsv.
 04759                    mainFilters.Add("hwmap=derive_device=qsv");
 04760                    mainFilters.Add("format=qsv");
 4761                }
 4762            }
 4763
 04764            if (doOclTonemap && isHwDecoder)
 4765            {
 4766                // map from qsv to opencl via qsv(vaapi)-opencl interop.
 04767                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4768            }
 4769
 4770            // ocl tonemap
 04771            if (doOclTonemap)
 4772            {
 04773                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04774                mainFilters.Add(tonemapFilter);
 4775            }
 4776
 04777            var memoryOutput = false;
 04778            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04779            var isHwmapUsable = isSwEncoder && (doOclTonemap || isVaapiDecoder);
 04780            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4781            {
 04782                memoryOutput = true;
 4783
 4784                // OUTPUT nv12 surface(memory)
 4785                // prefer hwmap to hwdownload on opencl/vaapi.
 4786                // qsv hwmap is not fully implemented for the time being.
 04787                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04788                mainFilters.Add("format=nv12");
 4789            }
 4790
 4791            // OUTPUT nv12 surface(memory)
 04792            if (isSwDecoder && isQsvEncoder)
 4793            {
 04794                memoryOutput = true;
 4795            }
 4796
 04797            if (memoryOutput)
 4798            {
 4799                // text subtitles
 04800                if (hasTextSubs)
 4801                {
 04802                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04803                    mainFilters.Add(textSubtitlesFilter);
 4804                }
 4805            }
 4806
 04807            if (isQsvInQsvOut)
 4808            {
 04809                if (doOclTonemap)
 4810                {
 4811                    // OUTPUT qsv(nv12) surface(vram)
 4812                    // reverse-mapping via qsv(vaapi)-opencl interop.
 4813                    // add extra pool size to avoid the 'cannot allocate memory' error on hevc_qsv.
 04814                    mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1:extra_hw_frames=16");
 04815                    mainFilters.Add("format=qsv");
 4816                }
 04817                else if (isVaapiDecoder)
 4818                {
 04819                    mainFilters.Add("hwmap=derive_device=qsv");
 04820                    mainFilters.Add("format=qsv");
 4821                }
 4822            }
 4823
 4824            /* Make sub and overlay filters for subtitle stream */
 04825            var subFilters = new List<string>();
 04826            var overlayFilters = new List<string>();
 04827            if (isQsvInQsvOut)
 4828            {
 04829                if (hasSubs)
 4830                {
 04831                    if (hasGraphicalSubs)
 4832                    {
 4833                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04834                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04835                        subFilters.Add(subPreProcFilters);
 04836                        subFilters.Add("format=bgra");
 4837                    }
 04838                    else if (hasTextSubs)
 4839                    {
 04840                        var framerate = state.VideoStream?.RealFrameRate;
 04841                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4842
 04843                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04844                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04845                        subFilters.Add(alphaSrcFilter);
 04846                        subFilters.Add("format=bgra");
 04847                        subFilters.Add(subTextSubtitlesFilter);
 4848                    }
 4849
 4850                    // qsv requires a fixed pool size.
 4851                    // default to 64 otherwise it will fail on certain iGPU.
 04852                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4853
 04854                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04855                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04856                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04857                        : string.Empty;
 04858                    var overlayQsvFilter = string.Format(
 04859                        CultureInfo.InvariantCulture,
 04860                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04861                        overlaySize);
 04862                    overlayFilters.Add(overlayQsvFilter);
 4863                }
 4864            }
 04865            else if (memoryOutput)
 4866            {
 04867                if (hasGraphicalSubs)
 4868                {
 04869                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04870                    subFilters.Add(subPreProcFilters);
 04871                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4872                }
 4873            }
 4874
 04875            return (mainFilters, subFilters, overlayFilters);
 4876        }
 4877
 4878        /// <summary>
 4879        /// Gets the parameter of Intel/AMD VAAPI filter chain.
 4880        /// </summary>
 4881        /// <param name="state">Encoding state.</param>
 4882        /// <param name="options">Encoding options.</param>
 4883        /// <param name="vidEncoder">Video encoder to use.</param>
 4884        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4885        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiVidFilterChain(
 4886            EncodingJobInfo state,
 4887            EncodingOptions options,
 4888            string vidEncoder)
 4889        {
 04890            if (options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 4891            {
 04892                return (null, null, null);
 4893            }
 4894
 04895            var isLinux = OperatingSystem.IsLinux();
 04896            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04897            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04898            var isSwEncoder = !vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04899            var isVaapiFullSupported = isLinux && IsVaapiSupported(state) && IsVaapiFullSupported();
 04900            var isVaapiOclSupported = isVaapiFullSupported && IsOpenclFullSupported();
 04901            var isVaapiVkSupported = isVaapiFullSupported && IsVulkanFullSupported();
 4902
 4903            // legacy vaapi pipeline(copy-back)
 04904            if ((isSwDecoder && isSwEncoder)
 04905                || !isVaapiOclSupported
 04906                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4907            {
 04908                var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
 4909
 04910                if (!isSwEncoder)
 4911                {
 04912                    var newfilters = new List<string>();
 04913                    var noOverlay = swFilterChain.OverlayFilters.Count == 0;
 04914                    newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
 04915                    newfilters.Add("hwupload=derive_device=vaapi");
 4916
 04917                    var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
 04918                    var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
 04919                    return (mainFilters, swFilterChain.SubFilters, overlayFilters);
 4920                }
 4921
 04922                return swFilterChain;
 4923            }
 4924
 4925            // preferred vaapi + opencl filters pipeline
 04926            if (_mediaEncoder.IsVaapiDeviceInteliHD)
 4927            {
 4928                // Intel iHD path, with extra vpp tonemap and overlay support.
 04929                return GetIntelVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4930            }
 4931
 4932            // preferred vaapi + vulkan filters pipeline
 04933            if (_mediaEncoder.IsVaapiDeviceAmd
 04934                && isVaapiVkSupported
 04935                && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 04936                && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 4937            {
 4938                // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support.
 04939                return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4940            }
 4941
 4942            // Intel i965 and Amd legacy driver path, only featuring scale and deinterlace support.
 04943            return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4944        }
 4945
 4946        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVaapiFullVidFilt
 4947            EncodingJobInfo state,
 4948            EncodingOptions options,
 4949            string vidDecoder,
 4950            string vidEncoder)
 4951        {
 04952            var inW = state.VideoStream?.Width;
 04953            var inH = state.VideoStream?.Height;
 04954            var reqW = state.BaseRequest.Width;
 04955            var reqH = state.BaseRequest.Height;
 04956            var reqMaxW = state.BaseRequest.MaxWidth;
 04957            var reqMaxH = state.BaseRequest.MaxHeight;
 04958            var threeDFormat = state.MediaSource.Video3DFormat;
 4959
 04960            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04961            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04962            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04963            var isSwEncoder = !isVaapiEncoder;
 04964            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04965            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 4966
 04967            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04968            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04969            var doVaVppTonemap = isVaapiDecoder && IsIntelVppTonemapAvailable(state, options);
 04970            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04971            var doTonemap = doVaVppTonemap || doOclTonemap;
 04972            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4973
 04974            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04975            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04976            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04977            var hasAssSubs = hasSubs
 04978                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04979                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04980            var subW = state.SubtitleStream?.Width;
 04981            var subH = state.SubtitleStream?.Height;
 4982
 04983            var rotation = state.VideoStream?.Rotation ?? 0;
 04984            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04985            var doVaVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04986            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVaVppTranspose));
 04987            var swpInW = swapWAndH ? inH : inW;
 04988            var swpInH = swapWAndH ? inW : inH;
 4989
 4990            /* Make main filters for video stream */
 04991            var mainFilters = new List<string>();
 4992
 04993            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4994
 04995            if (isSwDecoder)
 4996            {
 4997                // INPUT sw surface(memory)
 4998                // sw deint
 04999                if (doDeintH2645)
 5000                {
 05001                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05002                    mainFilters.Add(swDeintFilter);
 5003                }
 5004
 05005                var outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05006                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05007                if (isMjpegEncoder && !doOclTonemap)
 5008                {
 5009                    // sw decoder + hw mjpeg encoder
 05010                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5011                }
 5012
 5013                // sw scale
 05014                mainFilters.Add(swScaleFilter);
 05015                mainFilters.Add($"format={outFormat}");
 5016
 5017                // keep video at memory except ocl tonemap,
 5018                // since the overhead caused by hwupload >>> using sw filter.
 5019                // sw => hw
 05020                if (doOclTonemap)
 5021                {
 05022                    mainFilters.Add("hwupload=derive_device=opencl");
 5023                }
 5024            }
 05025            else if (isVaapiDecoder)
 5026            {
 05027                var isRext = IsVideoStreamHevcRext(state);
 5028
 5029                // INPUT vaapi surface(vram)
 5030                // hw deint
 05031                if (doDeintH2645)
 5032                {
 05033                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05034                    mainFilters.Add(deintFilter);
 5035                }
 5036
 5037                // hw transpose
 05038                if (doVaVppTranspose)
 5039                {
 05040                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 5041                }
 5042
 05043                var outFormat = doTonemap ? (isRext ? "p010" : string.Empty) : "nv12";
 05044                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, swpInW, swpInH, reqW, reqH, req
 5045
 05046                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5047                {
 05048                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05049                    hwScaleFilter += ":mode=hq";
 5050                }
 5051
 5052                // allocate extra pool sizes for vaapi vpp
 05053                if (!string.IsNullOrEmpty(hwScaleFilter))
 5054                {
 05055                    hwScaleFilter += ":extra_hw_frames=24";
 5056                }
 5057
 5058                // hw scale
 05059                mainFilters.Add(hwScaleFilter);
 5060            }
 5061
 5062            // vaapi vpp tonemap
 05063            if (doVaVppTonemap && isVaapiDecoder)
 5064            {
 05065                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 05066                mainFilters.Add(tonemapFilter);
 5067            }
 5068
 05069            if (doOclTonemap && isVaapiDecoder)
 5070            {
 5071                // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05072                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 5073            }
 5074
 5075            // ocl tonemap
 05076            if (doOclTonemap)
 5077            {
 05078                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05079                mainFilters.Add(tonemapFilter);
 5080            }
 5081
 05082            if (doOclTonemap && isVaInVaOut)
 5083            {
 5084                // OUTPUT vaapi(nv12) surface(vram)
 5085                // reverse-mapping via vaapi-opencl interop.
 05086                mainFilters.Add("hwmap=derive_device=vaapi:mode=write:reverse=1");
 05087                mainFilters.Add("format=vaapi");
 5088            }
 5089
 05090            var memoryOutput = false;
 05091            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 05092            var isHwmapNotUsable = isUploadForOclTonemap && isVaapiEncoder;
 05093            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap)
 5094            {
 05095                memoryOutput = true;
 5096
 5097                // OUTPUT nv12 surface(memory)
 5098                // prefer hwmap to hwdownload on opencl/vaapi.
 05099                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap=mode=read");
 05100                mainFilters.Add("format=nv12");
 5101            }
 5102
 5103            // OUTPUT nv12 surface(memory)
 05104            if (isSwDecoder && isVaapiEncoder)
 5105            {
 05106                memoryOutput = true;
 5107            }
 5108
 05109            if (memoryOutput)
 5110            {
 5111                // text subtitles
 05112                if (hasTextSubs)
 5113                {
 05114                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05115                    mainFilters.Add(textSubtitlesFilter);
 5116                }
 5117            }
 5118
 05119            if (memoryOutput && isVaapiEncoder)
 5120            {
 05121                if (!hasGraphicalSubs)
 5122                {
 05123                    mainFilters.Add("hwupload_vaapi");
 5124                }
 5125            }
 5126
 5127            /* Make sub and overlay filters for subtitle stream */
 05128            var subFilters = new List<string>();
 05129            var overlayFilters = new List<string>();
 05130            if (isVaInVaOut)
 5131            {
 05132                if (hasSubs)
 5133                {
 05134                    if (hasGraphicalSubs)
 5135                    {
 5136                        // overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead
 05137                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 05138                        subFilters.Add(subPreProcFilters);
 05139                        subFilters.Add("format=bgra");
 5140                    }
 05141                    else if (hasTextSubs)
 5142                    {
 05143                        var framerate = state.VideoStream?.RealFrameRate;
 05144                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5145
 05146                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 05147                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05148                        subFilters.Add(alphaSrcFilter);
 05149                        subFilters.Add("format=bgra");
 05150                        subFilters.Add(subTextSubtitlesFilter);
 5151                    }
 5152
 05153                    subFilters.Add("hwupload=derive_device=vaapi");
 5154
 05155                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 05156                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 05157                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 05158                        : string.Empty;
 05159                    var overlayVaapiFilter = string.Format(
 05160                        CultureInfo.InvariantCulture,
 05161                        "overlay_vaapi=eof_action=pass:repeatlast=0{0}",
 05162                        overlaySize);
 05163                    overlayFilters.Add(overlayVaapiFilter);
 5164                }
 5165            }
 05166            else if (memoryOutput)
 5167            {
 05168                if (hasGraphicalSubs)
 5169                {
 05170                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05171                    subFilters.Add(subPreProcFilters);
 05172                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5173
 05174                    if (isVaapiEncoder)
 5175                    {
 05176                        overlayFilters.Add("hwupload_vaapi");
 5177                    }
 5178                }
 5179            }
 5180
 05181            return (mainFilters, subFilters, overlayFilters);
 5182        }
 5183
 5184        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVaapiFullVidFilter
 5185            EncodingJobInfo state,
 5186            EncodingOptions options,
 5187            string vidDecoder,
 5188            string vidEncoder)
 5189        {
 05190            var inW = state.VideoStream?.Width;
 05191            var inH = state.VideoStream?.Height;
 05192            var reqW = state.BaseRequest.Width;
 05193            var reqH = state.BaseRequest.Height;
 05194            var reqMaxW = state.BaseRequest.MaxWidth;
 05195            var reqMaxH = state.BaseRequest.MaxHeight;
 05196            var threeDFormat = state.MediaSource.Video3DFormat;
 5197
 05198            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05199            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05200            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05201            var isSwEncoder = !isVaapiEncoder;
 05202            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5203
 05204            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05205            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05206            var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
 05207            var doDeintH2645 = doDeintH264 || doDeintHevc;
 5208
 05209            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05210            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05211            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05212            var hasAssSubs = hasSubs
 05213                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05214                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5215
 05216            var rotation = state.VideoStream?.Rotation ?? 0;
 05217            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05218            var doVkTranspose = isVaapiDecoder && !string.IsNullOrEmpty(transposeDir);
 05219            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVkTranspose));
 05220            var swpInW = swapWAndH ? inH : inW;
 05221            var swpInH = swapWAndH ? inW : inH;
 5222
 5223            /* Make main filters for video stream */
 05224            var mainFilters = new List<string>();
 5225
 05226            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doVkTonemap));
 5227
 05228            if (isSwDecoder)
 5229            {
 5230                // INPUT sw surface(memory)
 5231                // sw deint
 05232                if (doDeintH2645)
 5233                {
 05234                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05235                    mainFilters.Add(swDeintFilter);
 5236                }
 5237
 05238                if (doVkTonemap || hasSubs)
 5239                {
 5240                    // sw => hw
 05241                    mainFilters.Add("hwupload=derive_device=vulkan");
 05242                    mainFilters.Add("format=vulkan");
 5243                }
 5244                else
 5245                {
 5246                    // sw scale
 05247                    var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW,
 05248                    mainFilters.Add(swScaleFilter);
 05249                    mainFilters.Add("format=nv12");
 5250                }
 5251            }
 05252            else if (isVaapiDecoder)
 5253            {
 5254                // INPUT vaapi surface(vram)
 05255                if (doVkTranspose || doVkTonemap || hasSubs)
 5256                {
 5257                    // map from vaapi to vulkan/drm via interop (Polaris/gfx8+).
 05258                    if (_mediaEncoder.EncoderVersion >= _minFFmpegAlteredVaVkInterop)
 5259                    {
 05260                        if (doVkTranspose || !_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5261                        {
 5262                            // disable the indirect va-drm-vk mapping since it's no longer reliable.
 05263                            mainFilters.Add("hwmap=derive_device=drm");
 05264                            mainFilters.Add("format=drm_prime");
 05265                            mainFilters.Add("hwmap=derive_device=vulkan");
 05266                            mainFilters.Add("format=vulkan");
 5267
 5268                            // workaround for libplacebo using the imported vulkan frame on gfx8.
 05269                            if (!_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5270                            {
 05271                                mainFilters.Add("scale_vulkan");
 5272                            }
 5273                        }
 05274                        else if (doVkTonemap || hasSubs)
 5275                        {
 5276                            // non ad-hoc libplacebo also accepts drm_prime direct input.
 05277                            mainFilters.Add("hwmap=derive_device=drm");
 05278                            mainFilters.Add("format=drm_prime");
 5279                        }
 5280                    }
 5281                    else // legacy va-vk mapping that works only in jellyfin-ffmpeg6
 5282                    {
 05283                        mainFilters.Add("hwmap=derive_device=vulkan");
 05284                        mainFilters.Add("format=vulkan");
 5285                    }
 5286                }
 5287                else
 5288                {
 5289                    // hw deint
 05290                    if (doDeintH2645)
 5291                    {
 05292                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05293                        mainFilters.Add(deintFilter);
 5294                    }
 5295
 5296                    // hw scale
 05297                    var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", "nv12", false, inW, inH, reqW, reqH, reqMaxW,
 5298
 05299                    if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder && !doVkTonemap)
 5300                    {
 05301                        hwScaleFilter += ":out_range=pc:mode=hq";
 5302                    }
 5303
 05304                    mainFilters.Add(hwScaleFilter);
 5305                }
 5306            }
 5307
 5308            // vk transpose
 05309            if (doVkTranspose)
 5310            {
 05311                if (string.Equals(transposeDir, "reversal", StringComparison.OrdinalIgnoreCase))
 5312                {
 05313                    mainFilters.Add("flip_vulkan");
 5314                }
 5315                else
 5316                {
 05317                    mainFilters.Add($"transpose_vulkan=dir={transposeDir}");
 5318                }
 5319            }
 5320
 5321            // vk libplacebo
 05322            if (doVkTonemap || hasSubs)
 5323            {
 05324                var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, req
 05325                mainFilters.Add(libplaceboFilter);
 05326                mainFilters.Add("format=vulkan");
 5327            }
 5328
 05329            if (doVkTonemap && !hasSubs)
 5330            {
 5331                // OUTPUT vaapi(nv12) surface(vram)
 5332                // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05333                mainFilters.Add("hwmap=derive_device=vaapi");
 05334                mainFilters.Add("format=vaapi");
 5335
 5336                // clear the surf->meta_offset and output nv12
 05337                mainFilters.Add("scale_vaapi=format=nv12");
 5338
 5339                // hw deint
 05340                if (doDeintH2645)
 5341                {
 05342                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05343                    mainFilters.Add(deintFilter);
 5344                }
 5345            }
 5346
 05347            if (!hasSubs)
 5348            {
 5349                // OUTPUT nv12 surface(memory)
 05350                if (isSwEncoder && (doVkTonemap || isVaapiDecoder))
 5351                {
 05352                    mainFilters.Add("hwdownload");
 05353                    mainFilters.Add("format=nv12");
 5354                }
 5355
 05356                if (isSwDecoder && isVaapiEncoder && !doVkTonemap)
 5357                {
 05358                    mainFilters.Add("hwupload_vaapi");
 5359                }
 5360            }
 5361
 5362            /* Make sub and overlay filters for subtitle stream */
 05363            var subFilters = new List<string>();
 05364            var overlayFilters = new List<string>();
 05365            if (hasSubs)
 5366            {
 05367                if (hasGraphicalSubs)
 5368                {
 05369                    var subW = state.SubtitleStream?.Width;
 05370                    var subH = state.SubtitleStream?.Height;
 05371                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05372                    subFilters.Add(subPreProcFilters);
 05373                    subFilters.Add("format=bgra");
 5374                }
 05375                else if (hasTextSubs)
 5376                {
 05377                    var framerate = state.VideoStream?.RealFrameRate;
 05378                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5379
 05380                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05381                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05382                    subFilters.Add(alphaSrcFilter);
 05383                    subFilters.Add("format=bgra");
 05384                    subFilters.Add(subTextSubtitlesFilter);
 5385                }
 5386
 05387                subFilters.Add("hwupload=derive_device=vulkan");
 05388                subFilters.Add("format=vulkan");
 5389
 05390                overlayFilters.Add("overlay_vulkan=eof_action=pass:repeatlast=0");
 5391
 05392                if (isSwEncoder)
 5393                {
 5394                    // OUTPUT nv12 surface(memory)
 05395                    overlayFilters.Add("scale_vulkan=format=nv12");
 05396                    overlayFilters.Add("hwdownload");
 05397                    overlayFilters.Add("format=nv12");
 5398                }
 05399                else if (isVaapiEncoder)
 5400                {
 5401                    // OUTPUT vaapi(nv12) surface(vram)
 5402                    // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05403                    overlayFilters.Add("hwmap=derive_device=vaapi");
 05404                    overlayFilters.Add("format=vaapi");
 5405
 5406                    // clear the surf->meta_offset and output nv12
 05407                    overlayFilters.Add("scale_vaapi=format=nv12");
 5408
 5409                    // hw deint
 05410                    if (doDeintH2645)
 5411                    {
 05412                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05413                        overlayFilters.Add(deintFilter);
 5414                    }
 5415                }
 5416            }
 5417
 05418            return (mainFilters, subFilters, overlayFilters);
 5419        }
 5420
 5421        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiLimitedVidFilter
 5422            EncodingJobInfo state,
 5423            EncodingOptions options,
 5424            string vidDecoder,
 5425            string vidEncoder)
 5426        {
 05427            var inW = state.VideoStream?.Width;
 05428            var inH = state.VideoStream?.Height;
 05429            var reqW = state.BaseRequest.Width;
 05430            var reqH = state.BaseRequest.Height;
 05431            var reqMaxW = state.BaseRequest.MaxWidth;
 05432            var reqMaxH = state.BaseRequest.MaxHeight;
 05433            var threeDFormat = state.MediaSource.Video3DFormat;
 5434
 05435            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05436            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05437            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05438            var isSwEncoder = !isVaapiEncoder;
 05439            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05440            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 05441            var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965;
 05442            var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd;
 5443
 05444            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05445            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05446            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05447            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5448
 05449            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05450            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05451            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 5452
 05453            var rotation = state.VideoStream?.Rotation ?? 0;
 05454            var swapWAndH = Math.Abs(rotation) == 90 && isSwDecoder;
 05455            var swpInW = swapWAndH ? inH : inW;
 05456            var swpInH = swapWAndH ? inW : inH;
 5457
 5458            /* Make main filters for video stream */
 05459            var mainFilters = new List<string>();
 5460
 05461            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5462
 05463            var outFormat = string.Empty;
 05464            if (isSwDecoder)
 5465            {
 5466                // INPUT sw surface(memory)
 5467                // sw deint
 05468                if (doDeintH2645)
 5469                {
 05470                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05471                    mainFilters.Add(swDeintFilter);
 5472                }
 5473
 05474                outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05475                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05476                if (isMjpegEncoder && !doOclTonemap)
 5477                {
 5478                    // sw decoder + hw mjpeg encoder
 05479                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5480                }
 5481
 5482                // sw scale
 05483                mainFilters.Add(swScaleFilter);
 05484                mainFilters.Add("format=" + outFormat);
 5485
 5486                // keep video at memory except ocl tonemap,
 5487                // since the overhead caused by hwupload >>> using sw filter.
 5488                // sw => hw
 05489                if (doOclTonemap)
 5490                {
 05491                    mainFilters.Add("hwupload=derive_device=opencl");
 5492                }
 5493            }
 05494            else if (isVaapiDecoder)
 5495            {
 5496                // INPUT vaapi surface(vram)
 5497                // hw deint
 05498                if (doDeintH2645)
 5499                {
 05500                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05501                    mainFilters.Add(deintFilter);
 5502                }
 5503
 05504                outFormat = doOclTonemap ? string.Empty : "nv12";
 05505                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, inW, inH, reqW, reqH, reqMaxW, 
 5506
 05507                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5508                {
 05509                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05510                    hwScaleFilter += ":mode=hq";
 5511                }
 5512
 5513                // allocate extra pool sizes for vaapi vpp
 05514                if (!string.IsNullOrEmpty(hwScaleFilter))
 5515                {
 05516                    hwScaleFilter += ":extra_hw_frames=24";
 5517                }
 5518
 5519                // hw scale
 05520                mainFilters.Add(hwScaleFilter);
 5521            }
 5522
 05523            if (doOclTonemap && isVaapiDecoder)
 5524            {
 05525                if (isi965Driver)
 5526                {
 5527                    // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05528                    mainFilters.Add("hwmap=derive_device=opencl");
 5529                }
 5530                else
 5531                {
 05532                    mainFilters.Add("hwdownload");
 05533                    mainFilters.Add("format=p010le");
 05534                    mainFilters.Add("hwupload=derive_device=opencl");
 5535                }
 5536            }
 5537
 5538            // ocl tonemap
 05539            if (doOclTonemap)
 5540            {
 05541                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05542                mainFilters.Add(tonemapFilter);
 5543            }
 5544
 05545            if (doOclTonemap && isVaInVaOut)
 5546            {
 05547                if (isi965Driver)
 5548                {
 5549                    // OUTPUT vaapi(nv12) surface(vram)
 5550                    // reverse-mapping via vaapi-opencl interop.
 05551                    mainFilters.Add("hwmap=derive_device=vaapi:reverse=1");
 05552                    mainFilters.Add("format=vaapi");
 5553                }
 5554            }
 5555
 05556            var memoryOutput = false;
 05557            var isUploadForOclTonemap = doOclTonemap && (isSwDecoder || (isVaapiDecoder && !isi965Driver));
 05558            var isHwmapNotUsable = hasGraphicalSubs || isUploadForOclTonemap;
 05559            var isHwmapForSubs = hasSubs && isVaapiDecoder;
 05560            var isHwUnmapForTextSubs = hasTextSubs && isVaInVaOut && !isUploadForOclTonemap;
 05561            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap || isHwmapForSubs)
 5562            {
 05563                memoryOutput = true;
 5564
 5565                // OUTPUT nv12 surface(memory)
 5566                // prefer hwmap to hwdownload on opencl/vaapi.
 05567                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap");
 05568                mainFilters.Add("format=nv12");
 5569            }
 5570
 5571            // OUTPUT nv12 surface(memory)
 05572            if (isSwDecoder && isVaapiEncoder)
 5573            {
 05574                memoryOutput = true;
 5575            }
 5576
 05577            if (memoryOutput)
 5578            {
 5579                // text subtitles
 05580                if (hasTextSubs)
 5581                {
 05582                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05583                    mainFilters.Add(textSubtitlesFilter);
 5584                }
 5585            }
 5586
 05587            if (isHwUnmapForTextSubs)
 5588            {
 05589                mainFilters.Add("hwmap");
 05590                mainFilters.Add("format=vaapi");
 5591            }
 05592            else if (memoryOutput && isVaapiEncoder)
 5593            {
 05594                if (!hasGraphicalSubs)
 5595                {
 05596                    mainFilters.Add("hwupload_vaapi");
 5597                }
 5598            }
 5599
 5600            /* Make sub and overlay filters for subtitle stream */
 05601            var subFilters = new List<string>();
 05602            var overlayFilters = new List<string>();
 05603            if (memoryOutput)
 5604            {
 05605                if (hasGraphicalSubs)
 5606                {
 05607                    var subW = state.SubtitleStream?.Width;
 05608                    var subH = state.SubtitleStream?.Height;
 05609                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05610                    subFilters.Add(subPreProcFilters);
 05611                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5612
 05613                    if (isVaapiEncoder)
 5614                    {
 05615                        overlayFilters.Add("hwupload_vaapi");
 5616                    }
 5617                }
 5618            }
 5619
 05620            return (mainFilters, subFilters, overlayFilters);
 5621        }
 5622
 5623        /// <summary>
 5624        /// Gets the parameter of Apple VideoToolBox filter chain.
 5625        /// </summary>
 5626        /// <param name="state">Encoding state.</param>
 5627        /// <param name="options">Encoding options.</param>
 5628        /// <param name="vidEncoder">Video encoder to use.</param>
 5629        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5630        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFilterChain(
 5631            EncodingJobInfo state,
 5632            EncodingOptions options,
 5633            string vidEncoder)
 5634        {
 05635            if (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 5636            {
 05637                return (null, null, null);
 5638            }
 5639
 5640            // ReSharper disable once InconsistentNaming
 05641            var isMacOS = OperatingSystem.IsMacOS();
 05642            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05643            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05644            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05645            var isVtFullSupported = isMacOS && IsVideoToolboxFullSupported();
 5646
 5647            // legacy videotoolbox pipeline (disable hw filters)
 05648            if (!(isVtEncoder || isVtDecoder)
 05649                || !isVtFullSupported
 05650                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5651            {
 05652                return GetSwVidFilterChain(state, options, vidEncoder);
 5653            }
 5654
 5655            // preferred videotoolbox + metal filters pipeline
 05656            return GetAppleVidFiltersPreferred(state, options, vidDecoder, vidEncoder);
 5657        }
 5658
 5659        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFiltersPrefer
 5660            EncodingJobInfo state,
 5661            EncodingOptions options,
 5662            string vidDecoder,
 5663            string vidEncoder)
 5664        {
 05665            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05666            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05667            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5668
 05669            var inW = state.VideoStream?.Width;
 05670            var inH = state.VideoStream?.Height;
 05671            var reqW = state.BaseRequest.Width;
 05672            var reqH = state.BaseRequest.Height;
 05673            var reqMaxW = state.BaseRequest.MaxWidth;
 05674            var reqMaxH = state.BaseRequest.MaxHeight;
 05675            var threeDFormat = state.MediaSource.Video3DFormat;
 5676
 05677            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05678            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05679            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05680            var doVtTonemap = IsVideoToolboxTonemapAvailable(state, options);
 05681            var doMetalTonemap = !doVtTonemap && IsHwTonemapAvailable(state, options);
 05682            var usingHwSurface = isVtDecoder && (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface);
 5683
 05684            var rotation = state.VideoStream?.Rotation ?? 0;
 05685            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05686            var doVtTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_vt");
 05687            var swapWAndH = Math.Abs(rotation) == 90 && doVtTranspose;
 05688            var swpInW = swapWAndH ? inH : inW;
 05689            var swpInH = swapWAndH ? inW : inH;
 5690
 05691            var scaleFormat = string.Empty;
 5692            // Use P010 for Metal tone mapping, otherwise force an 8bit output.
 05693            if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase))
 5694            {
 05695                if (doMetalTonemap)
 5696                {
 05697                    if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 5698                    {
 05699                        scaleFormat = "p010le";
 5700                    }
 5701                }
 5702                else
 5703                {
 05704                    scaleFormat = "nv12";
 5705                }
 5706            }
 5707
 05708            var hwScaleFilter = GetHwScaleFilter("scale", "vt", scaleFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW,
 5709
 05710            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05711            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05712            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05713            var hasAssSubs = hasSubs
 05714                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05715                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5716
 5717            /* Make main filters for video stream */
 05718            var mainFilters = new List<string>();
 5719
 5720            // hw deint
 05721            if (doDeintH2645)
 5722            {
 05723                var deintFilter = GetHwDeinterlaceFilter(state, options, "videotoolbox");
 05724                mainFilters.Add(deintFilter);
 5725            }
 5726
 5727            // hw transpose
 05728            if (doVtTranspose)
 5729            {
 05730                mainFilters.Add($"transpose_vt=dir={transposeDir}");
 5731            }
 5732
 05733            if (doVtTonemap)
 5734            {
 5735                const string VtTonemapArgs = "color_matrix=bt709:color_primaries=bt709:color_transfer=bt709";
 5736
 5737                // scale_vt can handle scaling & tonemapping in one shot, just like vpp_qsv.
 05738                hwScaleFilter = string.IsNullOrEmpty(hwScaleFilter)
 05739                    ? "scale_vt=" + VtTonemapArgs
 05740                    : hwScaleFilter + ":" + VtTonemapArgs;
 5741            }
 5742
 5743            // hw scale & vt tonemap
 05744            mainFilters.Add(hwScaleFilter);
 5745
 5746            // Metal tonemap
 05747            if (doMetalTonemap)
 5748            {
 05749                var tonemapFilter = GetHwTonemapFilter(options, "videotoolbox", "nv12", isMjpegEncoder);
 05750                mainFilters.Add(tonemapFilter);
 5751            }
 5752
 5753            /* Make sub and overlay filters for subtitle stream */
 05754            var subFilters = new List<string>();
 05755            var overlayFilters = new List<string>();
 5756
 05757            if (hasSubs)
 5758            {
 05759                if (hasGraphicalSubs)
 5760                {
 05761                    var subW = state.SubtitleStream?.Width;
 05762                    var subH = state.SubtitleStream?.Height;
 05763                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05764                    subFilters.Add(subPreProcFilters);
 05765                    subFilters.Add("format=bgra");
 5766                }
 05767                else if (hasTextSubs)
 5768                {
 05769                    var framerate = state.VideoStream?.RealFrameRate;
 05770                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5771
 05772                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05773                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05774                    subFilters.Add(alphaSrcFilter);
 05775                    subFilters.Add("format=bgra");
 05776                    subFilters.Add(subTextSubtitlesFilter);
 5777                }
 5778
 05779                subFilters.Add("hwupload");
 05780                overlayFilters.Add("overlay_videotoolbox=eof_action=pass:repeatlast=0");
 5781            }
 5782
 05783            if (usingHwSurface)
 5784            {
 05785                if (!isVtEncoder)
 5786                {
 05787                    mainFilters.Add("hwdownload");
 05788                    mainFilters.Add("format=nv12");
 5789                }
 5790
 05791                return (mainFilters, subFilters, overlayFilters);
 5792            }
 5793
 5794            // For old jellyfin-ffmpeg that has broken hwsurface, add a hwupload
 05795            var needFiltering = mainFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05796                                subFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05797                                overlayFilters.Any(f => !string.IsNullOrEmpty(f));
 05798            if (needFiltering)
 5799            {
 5800                // INPUT videotoolbox/memory surface(vram/uma)
 5801                // this will pass-through automatically if in/out format matches.
 05802                mainFilters.Insert(0, "hwupload");
 05803                mainFilters.Insert(0, "format=nv12|p010le|videotoolbox_vld");
 5804
 05805                if (!isVtEncoder)
 5806                {
 05807                    mainFilters.Add("hwdownload");
 05808                    mainFilters.Add("format=nv12");
 5809                }
 5810            }
 5811
 05812            return (mainFilters, subFilters, overlayFilters);
 5813        }
 5814
 5815        /// <summary>
 5816        /// Gets the parameter of Rockchip RKMPP/RKRGA filter chain.
 5817        /// </summary>
 5818        /// <param name="state">Encoding state.</param>
 5819        /// <param name="options">Encoding options.</param>
 5820        /// <param name="vidEncoder">Video encoder to use.</param>
 5821        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5822        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFilterChain(
 5823            EncodingJobInfo state,
 5824            EncodingOptions options,
 5825            string vidEncoder)
 5826        {
 05827            if (options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 5828            {
 05829                return (null, null, null);
 5830            }
 5831
 05832            var isLinux = OperatingSystem.IsLinux();
 05833            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05834            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05835            var isSwEncoder = !vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05836            var isRkmppOclSupported = isLinux && IsRkmppFullSupported() && IsOpenclFullSupported();
 5837
 05838            if ((isSwDecoder && isSwEncoder)
 05839                || !isRkmppOclSupported
 05840                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5841            {
 05842                return GetSwVidFilterChain(state, options, vidEncoder);
 5843            }
 5844
 5845            // preferred rkmpp + rkrga + opencl filters pipeline
 05846            if (isRkmppOclSupported)
 5847            {
 05848                return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5849            }
 5850
 05851            return (null, null, null);
 5852        }
 5853
 5854        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFiltersPrefer
 5855            EncodingJobInfo state,
 5856            EncodingOptions options,
 5857            string vidDecoder,
 5858            string vidEncoder)
 5859        {
 05860            var inW = state.VideoStream?.Width;
 05861            var inH = state.VideoStream?.Height;
 05862            var reqW = state.BaseRequest.Width;
 05863            var reqH = state.BaseRequest.Height;
 05864            var reqMaxW = state.BaseRequest.MaxWidth;
 05865            var reqMaxH = state.BaseRequest.MaxHeight;
 05866            var threeDFormat = state.MediaSource.Video3DFormat;
 5867
 05868            var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05869            var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05870            var isSwDecoder = !isRkmppDecoder;
 05871            var isSwEncoder = !isRkmppEncoder;
 05872            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05873            var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder;
 05874            var isEncoderSupportAfbc = isRkmppEncoder
 05875                && (vidEncoder.Contains("h264", StringComparison.OrdinalIgnoreCase)
 05876                    || vidEncoder.Contains("hevc", StringComparison.OrdinalIgnoreCase));
 5877
 05878            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05879            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05880            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05881            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5882
 05883            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05884            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05885            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05886            var hasAssSubs = hasSubs
 05887                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05888                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 05889            var subW = state.SubtitleStream?.Width;
 05890            var subH = state.SubtitleStream?.Height;
 5891
 05892            var rotation = state.VideoStream?.Rotation ?? 0;
 05893            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05894            var doRkVppTranspose = !string.IsNullOrEmpty(transposeDir);
 05895            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isRkmppDecoder && doRkVppTranspose));
 05896            var swpInW = swapWAndH ? inH : inW;
 05897            var swpInH = swapWAndH ? inW : inH;
 5898
 5899            /* Make main filters for video stream */
 05900            var mainFilters = new List<string>();
 5901
 05902            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5903
 05904            if (isSwDecoder)
 5905            {
 5906                // INPUT sw surface(memory)
 5907                // sw deint
 05908                if (doDeintH2645)
 5909                {
 05910                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05911                    mainFilters.Add(swDeintFilter);
 5912                }
 5913
 05914                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 05915                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05916                if (isMjpegEncoder && !doOclTonemap)
 5917                {
 5918                    // sw decoder + hw mjpeg encoder
 05919                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5920                }
 5921
 05922                if (!string.IsNullOrEmpty(swScaleFilter))
 5923                {
 05924                    swScaleFilter += ":flags=fast_bilinear";
 5925                }
 5926
 5927                // sw scale
 05928                mainFilters.Add(swScaleFilter);
 05929                mainFilters.Add($"format={outFormat}");
 5930
 5931                // keep video at memory except ocl tonemap,
 5932                // since the overhead caused by hwupload >>> using sw filter.
 5933                // sw => hw
 05934                if (doOclTonemap)
 5935                {
 05936                    mainFilters.Add("hwupload=derive_device=opencl");
 5937                }
 5938            }
 05939            else if (isRkmppDecoder)
 5940            {
 5941                // INPUT rkmpp/drm surface(gem/dma-heap)
 5942
 05943                var isFullAfbcPipeline = isEncoderSupportAfbc && isDrmInDrmOut && !doOclTonemap;
 05944                var swapOutputWandH = doRkVppTranspose && swapWAndH;
 05945                var outFormat = doOclTonemap ? "p010" : (isMjpegEncoder ? "bgra" : "nv12"); // RGA only support full ran
 05946                var hwScaleFilter = GetHwScaleFilter("vpp", "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, r
 05947                var doScaling = GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, swpInW, swpInH, reqW, re
 5948
 05949                if (!hasSubs
 05950                     || doRkVppTranspose
 05951                     || !isFullAfbcPipeline
 05952                     || !string.IsNullOrEmpty(doScaling))
 5953                {
 5954                    // RGA3 hardware only support (1/8 ~ 8) scaling in each blit operation,
 5955                    // but in Trickplay there's a case: (3840/320 == 12), enable 2pass for it
 05956                    if (!string.IsNullOrEmpty(doScaling)
 05957                        && !IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f))
 5958                    {
 5959                        // Vendor provided BSP kernel has an RGA driver bug that causes the output to be corrupted for P
 5960                        // Use NV15 instead of P010 to avoid the issue.
 5961                        // SDR inputs are using BGRA formats already which is not affected.
 05962                        var intermediateFormat = string.Equals(outFormat, "p010", StringComparison.OrdinalIgnoreCase) ? 
 05963                        var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={intermediateFormat}:force_o
 05964                        mainFilters.Add(hwScaleFilterFirstPass);
 5965                    }
 5966
 05967                    if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose)
 5968                    {
 05969                        hwScaleFilter += $":transpose={transposeDir}";
 5970                    }
 5971
 5972                    // try enabling AFBC to save DDR bandwidth
 05973                    if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline)
 5974                    {
 05975                        hwScaleFilter += ":afbc=1";
 5976                    }
 5977
 5978                    // hw transpose & scale
 05979                    mainFilters.Add(hwScaleFilter);
 5980                }
 5981            }
 5982
 05983            if (doOclTonemap && isRkmppDecoder)
 5984            {
 5985                // map from rkmpp/drm to opencl via drm-opencl interop.
 05986                mainFilters.Add("hwmap=derive_device=opencl");
 5987            }
 5988
 5989            // ocl tonemap
 05990            if (doOclTonemap)
 5991            {
 05992                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05993                mainFilters.Add(tonemapFilter);
 5994            }
 5995
 05996            var memoryOutput = false;
 05997            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 05998            if ((isRkmppDecoder && isSwEncoder) || isUploadForOclTonemap)
 5999            {
 06000                memoryOutput = true;
 6001
 6002                // OUTPUT nv12 surface(memory)
 06003                mainFilters.Add("hwdownload");
 06004                mainFilters.Add("format=nv12");
 6005            }
 6006
 6007            // OUTPUT nv12 surface(memory)
 06008            if (isSwDecoder && isRkmppEncoder)
 6009            {
 06010                memoryOutput = true;
 6011            }
 6012
 06013            if (memoryOutput)
 6014            {
 6015                // text subtitles
 06016                if (hasTextSubs)
 6017                {
 06018                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 06019                    mainFilters.Add(textSubtitlesFilter);
 6020                }
 6021            }
 6022
 06023            if (isDrmInDrmOut)
 6024            {
 06025                if (doOclTonemap)
 6026                {
 6027                    // OUTPUT drm(nv12) surface(gem/dma-heap)
 6028                    // reverse-mapping via drm-opencl interop.
 06029                    mainFilters.Add("hwmap=derive_device=rkmpp:reverse=1");
 06030                    mainFilters.Add("format=drm_prime");
 6031                }
 6032            }
 6033
 6034            /* Make sub and overlay filters for subtitle stream */
 06035            var subFilters = new List<string>();
 06036            var overlayFilters = new List<string>();
 06037            if (isDrmInDrmOut)
 6038            {
 06039                if (hasSubs)
 6040                {
 06041                    var subMaxH = 1080;
 06042                    if (hasGraphicalSubs)
 6043                    {
 06044                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 06045                        subFilters.Add(subPreProcFilters);
 06046                        subFilters.Add("format=bgra");
 6047                    }
 06048                    else if (hasTextSubs)
 6049                    {
 06050                        var framerate = state.VideoStream?.RealFrameRate;
 06051                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 6052
 6053                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 06054                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, subMaxH, subF
 06055                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 06056                        subFilters.Add(alphaSrcFilter);
 06057                        subFilters.Add("format=bgra");
 06058                        subFilters.Add(subTextSubtitlesFilter);
 6059                    }
 6060
 06061                    subFilters.Add("hwupload=derive_device=rkmpp");
 6062
 6063                    // offload 1080p+ subtitles swscale upscaling from CPU to RGA
 06064                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 06065                    if (overlayW.HasValue && overlayH.HasValue && overlayH.Value > subMaxH)
 6066                    {
 06067                        subFilters.Add($"vpp_rkrga=w={overlayW.Value}:h={overlayH.Value}:format=bgra:afbc=1");
 6068                    }
 6069
 6070                    // try enabling AFBC to save DDR bandwidth
 06071                    var hwOverlayFilter = "overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12";
 06072                    if (isEncoderSupportAfbc)
 6073                    {
 06074                        hwOverlayFilter += ":afbc=1";
 6075                    }
 6076
 06077                    overlayFilters.Add(hwOverlayFilter);
 6078                }
 6079            }
 06080            else if (memoryOutput)
 6081            {
 06082                if (hasGraphicalSubs)
 6083                {
 06084                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 06085                    subFilters.Add(subPreProcFilters);
 06086                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 6087                }
 6088            }
 6089
 06090            return (mainFilters, subFilters, overlayFilters);
 6091        }
 6092
 6093        /// <summary>
 6094        /// Gets the parameter of video processing filters.
 6095        /// </summary>
 6096        /// <param name="state">Encoding state.</param>
 6097        /// <param name="options">Encoding options.</param>
 6098        /// <param name="outputVideoCodec">Video codec to use.</param>
 6099        /// <returns>The video processing filters parameter.</returns>
 6100        public string GetVideoProcessingFilterParam(
 6101            EncodingJobInfo state,
 6102            EncodingOptions options,
 6103            string outputVideoCodec)
 6104        {
 06105            var videoStream = state.VideoStream;
 06106            if (videoStream is null)
 6107            {
 06108                return string.Empty;
 6109            }
 6110
 06111            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 06112            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 06113            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 6114
 6115            List<string> mainFilters;
 6116            List<string> subFilters;
 6117            List<string> overlayFilters;
 6118
 06119            (mainFilters, subFilters, overlayFilters) = options.HardwareAccelerationType switch
 06120            {
 06121                HardwareAccelerationType.vaapi => GetVaapiVidFilterChain(state, options, outputVideoCodec),
 06122                HardwareAccelerationType.amf => GetAmdVidFilterChain(state, options, outputVideoCodec),
 06123                HardwareAccelerationType.qsv => GetIntelVidFilterChain(state, options, outputVideoCodec),
 06124                HardwareAccelerationType.nvenc => GetNvidiaVidFilterChain(state, options, outputVideoCodec),
 06125                HardwareAccelerationType.videotoolbox => GetAppleVidFilterChain(state, options, outputVideoCodec),
 06126                HardwareAccelerationType.rkmpp => GetRkmppVidFilterChain(state, options, outputVideoCodec),
 06127                _ => GetSwVidFilterChain(state, options, outputVideoCodec),
 06128            };
 6129
 06130            mainFilters?.RemoveAll(string.IsNullOrEmpty);
 06131            subFilters?.RemoveAll(string.IsNullOrEmpty);
 06132            overlayFilters?.RemoveAll(string.IsNullOrEmpty);
 6133
 06134            var framerate = GetFramerateParam(state);
 06135            if (framerate.HasValue)
 6136            {
 06137                mainFilters.Insert(0, string.Format(
 06138                    CultureInfo.InvariantCulture,
 06139                    "fps={0}",
 06140                    framerate.Value));
 6141            }
 6142
 06143            var mainStr = string.Empty;
 06144            if (mainFilters?.Count > 0)
 6145            {
 06146                mainStr = string.Format(
 06147                    CultureInfo.InvariantCulture,
 06148                    "{0}",
 06149                    string.Join(',', mainFilters));
 6150            }
 6151
 06152            if (overlayFilters?.Count == 0)
 6153            {
 6154                // -vf "scale..."
 06155                return string.IsNullOrEmpty(mainStr) ? string.Empty : " -vf \"" + mainStr + "\"";
 6156            }
 6157
 06158            if (overlayFilters?.Count > 0
 06159                && subFilters?.Count > 0
 06160                && state.SubtitleStream is not null)
 6161            {
 6162                // overlay graphical/text subtitles
 06163                var subStr = string.Format(
 06164                        CultureInfo.InvariantCulture,
 06165                        "{0}",
 06166                        string.Join(',', subFilters));
 6167
 06168                var overlayStr = string.Format(
 06169                        CultureInfo.InvariantCulture,
 06170                        "{0}",
 06171                        string.Join(',', overlayFilters));
 6172
 06173                var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
 06174                var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 06175                var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 6176
 06177                if (hasSubs)
 6178                {
 6179                    // -filter_complex "[0:s]scale=s[sub]..."
 06180                    var filterStr = string.IsNullOrEmpty(mainStr)
 06181                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]{5}\""
 06182                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6183
 06184                    if (hasTextSubs)
 6185                    {
 06186                        filterStr = string.IsNullOrEmpty(mainStr)
 06187                            ? " -filter_complex \"{4}[sub];[0:{2}][sub]{5}\""
 06188                            : " -filter_complex \"{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6189                    }
 6190
 06191                    return string.Format(
 06192                        CultureInfo.InvariantCulture,
 06193                        filterStr,
 06194                        mapPrefix,
 06195                        subtitleStreamIndex,
 06196                        videoStreamIndex,
 06197                        mainStr,
 06198                        subStr,
 06199                        overlayStr);
 6200                }
 6201            }
 6202
 06203            return string.Empty;
 6204        }
 6205
 6206        public string GetOverwriteColorPropertiesParam(EncodingJobInfo state, bool isTonemapAvailable)
 6207        {
 06208            if (isTonemapAvailable)
 6209            {
 06210                return GetInputHdrParam(state.VideoStream?.ColorTransfer);
 6211            }
 6212
 06213            return GetOutputSdrParam(null);
 6214        }
 6215
 6216        public string GetInputHdrParam(string colorTransfer)
 6217        {
 06218            if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
 6219            {
 6220                // HLG
 06221                return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
 6222            }
 6223
 6224            // HDR10
 06225            return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc";
 6226        }
 6227
 6228        public string GetOutputSdrParam(string tonemappingRange)
 6229        {
 6230            // SDR
 06231            if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
 6232            {
 06233                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
 6234            }
 6235
 06236            if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
 6237            {
 06238                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc";
 6239            }
 6240
 06241            return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
 6242        }
 6243
 6244        public static int GetVideoColorBitDepth(EncodingJobInfo state)
 6245        {
 06246            var videoStream = state.VideoStream;
 06247            if (videoStream is not null)
 6248            {
 06249                if (videoStream.BitDepth.HasValue)
 6250                {
 06251                    return videoStream.BitDepth.Value;
 6252                }
 6253
 06254                if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
 06255                    || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
 06256                    || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 06257                    || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
 6258                {
 06259                    return 8;
 6260                }
 6261
 06262                if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 06263                    || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 06264                    || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
 6265                {
 06266                    return 10;
 6267                }
 6268
 06269                if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 06270                    || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 06271                    || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
 6272                {
 06273                    return 12;
 6274                }
 6275
 06276                return 8;
 6277            }
 6278
 06279            return 0;
 6280        }
 6281
 6282        /// <summary>
 6283        /// Gets the ffmpeg option string for the hardware accelerated video decoder.
 6284        /// </summary>
 6285        /// <param name="state">The encoding job info.</param>
 6286        /// <param name="options">The encoding options.</param>
 6287        /// <returns>The option string or null if none available.</returns>
 6288        protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
 6289        {
 06290            var videoStream = state.VideoStream;
 06291            var mediaSource = state.MediaSource;
 06292            if (videoStream is null || mediaSource is null)
 6293            {
 06294                return null;
 6295            }
 6296
 6297            // HWA decoders can handle both video files and video folders.
 06298            var videoType = state.VideoType;
 06299            if (videoType != VideoType.VideoFile
 06300                && videoType != VideoType.Iso
 06301                && videoType != VideoType.Dvd
 06302                && videoType != VideoType.BluRay)
 6303            {
 06304                return null;
 6305            }
 6306
 06307            if (IsCopyCodec(state.OutputVideoCodec))
 6308            {
 06309                return null;
 6310            }
 6311
 06312            var hardwareAccelerationType = options.HardwareAccelerationType;
 6313
 06314            if (!string.IsNullOrEmpty(videoStream.Codec) && hardwareAccelerationType != HardwareAccelerationType.none)
 6315            {
 06316                var bitDepth = GetVideoColorBitDepth(state);
 6317
 6318                // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support for most platforms
 06319                if (bitDepth == 10
 06320                    && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06321                         || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
 06322                         || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)
 06323                         || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)))
 6324                {
 6325                    // RKMPP has H.264 Hi10P decoder
 06326                    bool hasHardwareHi10P = hardwareAccelerationType == HardwareAccelerationType.rkmpp;
 6327
 6328                    // VideoToolbox on Apple Silicon has H.264 Hi10P mode enabled after macOS 14.6
 06329                    if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox)
 6330                    {
 06331                        var ver = Environment.OSVersion.Version;
 06332                        var arch = RuntimeInformation.OSArchitecture;
 06333                        if (arch.Equals(Architecture.Arm64) && ver >= new Version(14, 6))
 6334                        {
 06335                            hasHardwareHi10P = true;
 6336                        }
 6337                    }
 6338
 06339                    if (!hasHardwareHi10P
 06340                        && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6341                    {
 06342                        return null;
 6343                    }
 6344                }
 6345
 06346                var decoder = hardwareAccelerationType switch
 06347                {
 06348                    HardwareAccelerationType.vaapi => GetVaapiVidDecoder(state, options, videoStream, bitDepth),
 06349                    HardwareAccelerationType.amf => GetAmfVidDecoder(state, options, videoStream, bitDepth),
 06350                    HardwareAccelerationType.qsv => GetQsvHwVidDecoder(state, options, videoStream, bitDepth),
 06351                    HardwareAccelerationType.nvenc => GetNvdecVidDecoder(state, options, videoStream, bitDepth),
 06352                    HardwareAccelerationType.videotoolbox => GetVideotoolboxVidDecoder(state, options, videoStream, bitD
 06353                    HardwareAccelerationType.rkmpp => GetRkmppVidDecoder(state, options, videoStream, bitDepth),
 06354                    _ => string.Empty
 06355                };
 6356
 06357                if (!string.IsNullOrEmpty(decoder))
 6358                {
 06359                    return decoder;
 6360                }
 6361            }
 6362
 6363            // leave blank so ffmpeg will decide
 06364            return null;
 6365        }
 6366
 6367        /// <summary>
 6368        /// Gets a hw decoder name.
 6369        /// </summary>
 6370        /// <param name="options">Encoding options.</param>
 6371        /// <param name="decoderPrefix">Decoder prefix.</param>
 6372        /// <param name="decoderSuffix">Decoder suffix.</param>
 6373        /// <param name="videoCodec">Video codec to use.</param>
 6374        /// <param name="bitDepth">Video color bit depth.</param>
 6375        /// <returns>Hardware decoder name.</returns>
 6376        public string GetHwDecoderName(EncodingOptions options, string decoderPrefix, string decoderSuffix, string video
 6377        {
 06378            if (string.IsNullOrEmpty(decoderPrefix) || string.IsNullOrEmpty(decoderSuffix))
 6379            {
 06380                return null;
 6381            }
 6382
 06383            var decoderName = decoderPrefix + '_' + decoderSuffix;
 6384
 06385            var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains
 6386
 6387            // VideoToolbox decoders have built-in SW fallback
 06388            if (bitDepth == 10
 06389                && isCodecAvailable
 06390                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6391            {
 06392                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06393                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
 06394                    && !options.EnableDecodingColorDepth10Hevc)
 6395                {
 06396                    return null;
 6397                }
 6398
 06399                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06400                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06401                    && !options.EnableDecodingColorDepth10Vp9)
 6402                {
 06403                    return null;
 6404                }
 6405            }
 6406
 06407            if (string.Equals(decoderSuffix, "cuvid", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdec
 6408            {
 06409                return null;
 6410            }
 6411
 06412            if (string.Equals(decoderSuffix, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwD
 6413            {
 06414                return null;
 6415            }
 6416
 06417            if (string.Equals(decoderSuffix, "rkmpp", StringComparison.OrdinalIgnoreCase))
 6418            {
 06419                return null;
 6420            }
 6421
 06422            return isCodecAvailable ? (" -c:v " + decoderName) : null;
 6423        }
 6424
 6425        /// <summary>
 6426        /// Gets a hwaccel type to use as a hardware decoder depending on the system.
 6427        /// </summary>
 6428        /// <param name="state">Encoding state.</param>
 6429        /// <param name="options">Encoding options.</param>
 6430        /// <param name="videoCodec">Video codec to use.</param>
 6431        /// <param name="bitDepth">Video color bit depth.</param>
 6432        /// <param name="outputHwSurface">Specifies if output hw surface.</param>
 6433        /// <returns>Hardware accelerator type.</returns>
 6434        public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, int bitDepth, bo
 6435        {
 06436            var isWindows = OperatingSystem.IsWindows();
 06437            var isLinux = OperatingSystem.IsLinux();
 06438            var isMacOS = OperatingSystem.IsMacOS();
 06439            var isD3d11Supported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va");
 06440            var isVaapiSupported = isLinux && IsVaapiSupported(state);
 06441            var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported();
 06442            var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv");
 06443            var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
 06444            var isRkmppSupported = isLinux && IsRkmppFullSupported();
 06445            var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCas
 06446            var hardwareAccelerationType = options.HardwareAccelerationType;
 6447
 06448            var ffmpegVersion = _mediaEncoder.EncoderVersion;
 6449
 6450            // Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
 06451            var isAv1 = ffmpegVersion < _minFFmpegImplicitHwaccel
 06452                && string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
 6453
 6454            // Allow profile mismatch if decoding H.264 baseline with d3d11va and vaapi hwaccels.
 06455            var profileMismatch = string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
 06456                && string.Equals(state.VideoStream?.Profile, "baseline", StringComparison.OrdinalIgnoreCase);
 6457
 6458            // Disable the extra internal copy in nvdec. We already handle it in filter chain.
 06459            var nvdecNoInternalCopy = ffmpegVersion >= _minFFmpegHwaUnsafeOutput;
 6460
 6461            // Strip the display rotation side data from the transposed fmp4 output stream.
 06462            var stripRotationData = (state.VideoStream?.Rotation ?? 0) != 0
 06463                && ffmpegVersion >= _minFFmpegDisplayRotationOption;
 06464            var stripRotationDataArgs = stripRotationData ? " -display_rotation 0" : string.Empty;
 6465
 6466            // VideoToolbox decoders have built-in SW fallback
 06467            if (isCodecAvailable
 06468                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6469            {
 06470                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06471                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase))
 6472                {
 06473                    if (IsVideoStreamHevcRext(state))
 6474                    {
 06475                        if (bitDepth <= 10 && !options.EnableDecodingColorDepth10HevcRext)
 6476                        {
 06477                            return null;
 6478                        }
 6479
 06480                        if (bitDepth == 12 && !options.EnableDecodingColorDepth12HevcRext)
 6481                        {
 06482                            return null;
 6483                        }
 6484
 06485                        if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06486                            && !_mediaEncoder.IsVaapiDeviceInteliHD)
 6487                        {
 06488                            return null;
 6489                        }
 6490                    }
 06491                    else if (bitDepth == 10 && !options.EnableDecodingColorDepth10Hevc)
 6492                    {
 06493                        return null;
 6494                    }
 6495                }
 6496
 06497                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06498                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06499                    && bitDepth == 10
 06500                    && !options.EnableDecodingColorDepth10Vp9)
 6501                {
 06502                    return null;
 6503                }
 6504            }
 6505
 6506            // Intel qsv/d3d11va/vaapi
 06507            if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 6508            {
 06509                if (options.PreferSystemNativeHwDecoder)
 6510                {
 06511                    if (isVaapiSupported && isCodecAvailable)
 6512                    {
 06513                        return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + st
 06514                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " 
 6515                    }
 6516
 06517                    if (isD3d11Supported && isCodecAvailable)
 6518                    {
 06519                        return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + 
 06520                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 
 6521                    }
 6522                }
 6523                else
 6524                {
 06525                    if (isQsvSupported && isCodecAvailable)
 6526                    {
 06527                        return " -hwaccel qsv" + (outputHwSurface ? " -hwaccel_output_format qsv -noautorotate" + stripR
 6528                    }
 6529                }
 6530            }
 6531
 6532            // Nvidia cuda
 06533            if (hardwareAccelerationType == HardwareAccelerationType.nvenc)
 6534            {
 06535                if (isCudaSupported && isCodecAvailable)
 6536                {
 06537                    if (options.EnableEnhancedNvdecDecoder)
 6538                    {
 6539                        // set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support.
 06540                        return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stri
 06541                            + (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + 
 6542                    }
 6543
 6544                    // cuvid decoder doesn't have threading issue.
 06545                    return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stripRot
 6546                }
 6547            }
 6548
 6549            // Amd d3d11va
 06550            if (hardwareAccelerationType == HardwareAccelerationType.amf)
 6551            {
 06552                if (isD3d11Supported && isCodecAvailable)
 6553                {
 06554                    return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + stri
 06555                        + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v
 6556                }
 6557            }
 6558
 6559            // Vaapi
 06560            if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06561                && isVaapiSupported
 06562                && isCodecAvailable)
 6563            {
 06564                return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + stripRotat
 06565                    + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1
 6566            }
 6567
 6568            // Apple videotoolbox
 06569            if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06570                && isVideotoolboxSupported
 06571                && isCodecAvailable)
 6572            {
 06573                return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string
 6574            }
 6575
 6576            // Rockchip rkmpp
 06577            if (hardwareAccelerationType == HardwareAccelerationType.rkmpp
 06578                && isRkmppSupported
 06579                && isCodecAvailable)
 6580            {
 06581                return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime -noautorotate" + stripR
 6582            }
 6583
 06584            return null;
 6585        }
 6586
 6587        public string GetQsvHwVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6588        {
 06589            var isWindows = OperatingSystem.IsWindows();
 06590            var isLinux = OperatingSystem.IsLinux();
 6591
 06592            if ((!isWindows && !isLinux)
 06593                || options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 6594            {
 06595                return null;
 6596            }
 6597
 06598            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 06599            var isIntelDx11OclSupported = isWindows
 06600                && _mediaEncoder.SupportsHwaccel("d3d11va")
 06601                && isQsvOclSupported;
 06602            var isIntelVaapiOclSupported = isLinux
 06603                && IsVaapiSupported(state)
 06604                && isQsvOclSupported;
 06605            var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
 06606                && _mediaEncoder.SupportsFilter("alphasrc");
 6607
 06608            var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06609                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06610            var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 06611            var is8_10_12bitSwFormatsQsv = is8_10bitSwFormatsQsv
 06612                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06613                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06614                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06615                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06616                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06617                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06618                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6619            // TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
 6620
 06621            if (is8bitSwFormatsQsv)
 6622            {
 06623                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06624                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6625                {
 06626                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6627                }
 6628
 06629                if (string.Equals(videoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase))
 6630                {
 06631                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6632                }
 6633
 06634                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6635                {
 06636                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6637                }
 6638
 06639                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6640                {
 06641                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6642                }
 6643            }
 6644
 06645            if (is8_10bitSwFormatsQsv)
 6646            {
 06647                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6648                {
 06649                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6650                }
 6651
 06652                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 6653                {
 06654                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6655                }
 6656            }
 6657
 06658            if (is8_10_12bitSwFormatsQsv)
 6659            {
 06660                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06661                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6662                {
 06663                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6664                }
 6665            }
 6666
 06667            return null;
 6668        }
 6669
 6670        public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6671        {
 06672            if ((!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
 06673                || options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 6674            {
 06675                return null;
 6676            }
 6677
 06678            var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
 06679            var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06680                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06681            var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06682            var is8_10_12bitSwFormatsNvdec = is8_10bitSwFormatsNvdec
 06683                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06684                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06685                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06686                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6687            // TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
 6688
 06689            if (is8bitSwFormatsNvdec)
 6690            {
 06691                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06692                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6693                {
 06694                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6695                }
 6696
 06697                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6698                {
 06699                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6700                }
 6701
 06702                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6703                {
 06704                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6705                }
 6706
 06707                if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6708                {
 06709                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpe
 6710                }
 6711
 06712                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6713                {
 06714                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6715                }
 6716            }
 6717
 06718            if (is8_10bitSwFormatsNvdec)
 6719            {
 06720                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6721                {
 06722                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6723                }
 6724
 06725                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6726                {
 06727                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6728                }
 6729            }
 6730
 06731            if (is8_10_12bitSwFormatsNvdec)
 6732            {
 06733                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06734                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6735                {
 06736                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6737                }
 6738            }
 6739
 06740            return null;
 6741        }
 6742
 6743        public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitD
 6744        {
 06745            if (!OperatingSystem.IsWindows()
 06746                || options.HardwareAccelerationType != HardwareAccelerationType.amf)
 6747            {
 06748                return null;
 6749            }
 6750
 06751            var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
 06752                && IsOpenclFullSupported()
 06753                && _mediaEncoder.SupportsFilter("alphasrc");
 06754            var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06755                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06756            var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 6757
 06758            if (is8bitSwFormatsAmf)
 6759            {
 06760                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06761                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6762                {
 06763                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6764                }
 6765
 06766                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6767                {
 06768                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6769                }
 6770
 06771                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6772                {
 06773                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6774                }
 6775            }
 6776
 06777            if (is8_10bitSwFormatsAmf)
 6778            {
 06779                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06780                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6781                {
 06782                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6783                }
 6784
 06785                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6786                {
 06787                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6788                }
 6789
 06790                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6791                {
 06792                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6793                }
 6794            }
 6795
 06796            return null;
 6797        }
 6798
 6799        public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6800        {
 06801            if (!OperatingSystem.IsLinux()
 06802                || options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 6803            {
 06804                return null;
 6805            }
 6806
 06807            var hwSurface = IsVaapiSupported(state)
 06808                && IsVaapiFullSupported()
 06809                && IsOpenclFullSupported()
 06810                && _mediaEncoder.SupportsFilter("alphasrc");
 06811            var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06812                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06813            var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06814            var is8_10_12bitSwFormatsVaapi = is8_10bitSwFormatsVaapi
 06815                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06816                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06817                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06818                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06819                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06820                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06821                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6822
 06823            if (is8bitSwFormatsVaapi)
 6824            {
 06825                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06826                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6827                {
 06828                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6829                }
 6830
 06831                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6832                {
 06833                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6834                }
 6835
 06836                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6837                {
 06838                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6839                }
 6840
 06841                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6842                {
 06843                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6844                }
 6845            }
 6846
 06847            if (is8_10bitSwFormatsVaapi)
 6848            {
 06849                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6850                {
 06851                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6852                }
 6853
 06854                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6855                {
 06856                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6857                }
 6858            }
 6859
 06860            if (is8_10_12bitSwFormatsVaapi)
 6861            {
 06862                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06863                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6864                {
 06865                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6866                }
 6867            }
 6868
 06869            return null;
 6870        }
 6871
 6872        public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream,
 6873        {
 06874            if (!OperatingSystem.IsMacOS()
 06875                || options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 6876            {
 06877                return null;
 6878            }
 6879
 06880            var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase
 06881                                    || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnore
 06882            var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, String
 06883            var is8_10_12bitSwFormatsVt = is8_10bitSwFormatsVt
 06884                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06885                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06886                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06887                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06888                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06889                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06890                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 06891            var isAv1SupportedSwFormatsVt = is8_10bitSwFormatsVt || string.Equals("yuv420p12le", videoStream.PixelFormat
 6892
 6893            // The related patches make videotoolbox hardware surface working is only available in jellyfin-ffmpeg 7.0.1
 06894            bool useHwSurface = (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface) && IsVideoToolboxFullSupp
 6895
 06896            if (is8bitSwFormatsVt)
 6897            {
 06898                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6899                {
 06900                    return GetHwaccelType(state, options, "vp8", bitDepth, useHwSurface);
 6901                }
 6902            }
 6903
 06904            if (is8_10bitSwFormatsVt)
 6905            {
 06906                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06907                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6908                {
 06909                    return GetHwaccelType(state, options, "h264", bitDepth, useHwSurface);
 6910                }
 6911
 06912                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6913                {
 06914                    return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
 6915                }
 6916            }
 6917
 06918            if (is8_10_12bitSwFormatsVt)
 6919            {
 06920                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06921                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6922                {
 06923                    return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
 6924                }
 6925
 06926                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06927                    && isAv1SupportedSwFormatsVt
 06928                    && _mediaEncoder.IsVideoToolboxAv1DecodeAvailable)
 6929                {
 06930                    return GetHwaccelType(state, options, "av1", bitDepth, useHwSurface);
 6931                }
 6932            }
 6933
 06934            return null;
 6935        }
 6936
 6937        public string GetRkmppVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6938        {
 06939            var isLinux = OperatingSystem.IsLinux();
 6940
 06941            if (!isLinux
 06942                || options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 6943            {
 06944                return null;
 6945            }
 6946
 06947            var inW = state.VideoStream?.Width;
 06948            var inH = state.VideoStream?.Height;
 06949            var reqW = state.BaseRequest.Width;
 06950            var reqH = state.BaseRequest.Height;
 06951            var reqMaxW = state.BaseRequest.MaxWidth;
 06952            var reqMaxH = state.BaseRequest.MaxHeight;
 6953
 6954            // rkrga RGA2e supports range from 1/16 to 16
 06955            if (!IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 16.0f))
 6956            {
 06957                return null;
 6958            }
 6959
 06960            var isRkmppOclSupported = IsRkmppFullSupported() && IsOpenclFullSupported();
 06961            var hwSurface = isRkmppOclSupported
 06962                && _mediaEncoder.SupportsFilter("alphasrc");
 6963
 6964            // rkrga RGA3 supports range from 1/8 to 8
 06965            var isAfbcSupported = hwSurface && IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 6966
 6967            // TODO: add more 8/10bit and 4:2:2 formats for Rkmpp after finishing the ffcheck tool
 06968            var is8bitSwFormatsRkmpp = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06969                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06970            var is10bitSwFormatsRkmpp = string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIg
 06971            var is8_10bitSwFormatsRkmpp = is8bitSwFormatsRkmpp || is10bitSwFormatsRkmpp;
 6972
 6973            // nv15 and nv20 are bit-stream only formats
 06974            if (is10bitSwFormatsRkmpp && !hwSurface)
 6975            {
 06976                return null;
 6977            }
 6978
 06979            if (is8bitSwFormatsRkmpp)
 6980            {
 06981                if (string.Equals(videoStream.Codec, "mpeg1video", StringComparison.OrdinalIgnoreCase))
 6982                {
 06983                    return GetHwaccelType(state, options, "mpeg1video", bitDepth, hwSurface);
 6984                }
 6985
 06986                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6987                {
 06988                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6989                }
 6990
 06991                if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 6992                {
 06993                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
 6994                }
 6995
 06996                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6997                {
 06998                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6999                }
 7000            }
 7001
 07002            if (is8_10bitSwFormatsRkmpp)
 7003            {
 07004                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 07005                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 7006                {
 07007                    var accelType = GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 07008                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7009                }
 7010
 07011                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 07012                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 7013                {
 07014                    var accelType = GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 07015                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7016                }
 7017
 07018                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 7019                {
 07020                    var accelType = GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 07021                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7022                }
 7023
 07024                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 7025                {
 07026                    var accelType = GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 07027                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7028                }
 7029            }
 7030
 07031            return null;
 7032        }
 7033
 7034        /// <summary>
 7035        /// Gets the number of threads.
 7036        /// </summary>
 7037        /// <param name="state">Encoding state.</param>
 7038        /// <param name="encodingOptions">Encoding options.</param>
 7039        /// <param name="outputVideoCodec">Video codec to use.</param>
 7040        /// <returns>Number of threads.</returns>
 7041#nullable enable
 7042        public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVide
 7043        {
 07044            var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount;
 7045
 07046            if (threads <= 0)
 7047            {
 7048                // Automatically set thread count
 07049                return 0;
 7050            }
 7051
 07052            return Math.Min(threads, Environment.ProcessorCount);
 7053        }
 7054
 7055#nullable disable
 7056        public void TryStreamCopy(EncodingJobInfo state)
 7057        {
 07058            if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream))
 7059            {
 07060                state.OutputVideoCodec = "copy";
 7061            }
 7062            else
 7063            {
 07064                var user = state.User;
 7065
 7066                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07067                if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
 7068                {
 07069                    state.OutputVideoCodec = "copy";
 7070                }
 7071            }
 7072
 07073            if (state.AudioStream is not null
 07074                && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs))
 7075            {
 07076                state.OutputAudioCodec = "copy";
 7077            }
 7078            else
 7079            {
 07080                var user = state.User;
 7081
 7082                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07083                if (user is not null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
 7084                {
 07085                    state.OutputAudioCodec = "copy";
 7086                }
 7087            }
 07088        }
 7089
 7090        public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
 7091        {
 07092            var inputModifier = string.Empty;
 07093            var analyzeDurationArgument = string.Empty;
 7094
 7095            // Apply -analyzeduration as per the environment variable,
 7096            // otherwise ffmpeg will break on certain files due to default value is 0.
 07097            var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
 7098
 07099            if (state.MediaSource.AnalyzeDurationMs > 0)
 7100            {
 07101                analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToStr
 7102            }
 07103            else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
 7104            {
 07105                analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
 7106            }
 7107
 07108            if (!string.IsNullOrEmpty(analyzeDurationArgument))
 7109            {
 07110                inputModifier += " " + analyzeDurationArgument;
 7111            }
 7112
 07113            inputModifier = inputModifier.Trim();
 7114
 7115            // Apply -probesize if configured
 07116            var ffmpegProbeSize = _config.GetFFmpegProbeSize();
 7117
 07118            if (!string.IsNullOrEmpty(ffmpegProbeSize))
 7119            {
 07120                inputModifier += $" -probesize {ffmpegProbeSize}";
 7121            }
 7122
 07123            var userAgentParam = GetUserAgentParam(state);
 7124
 07125            if (!string.IsNullOrEmpty(userAgentParam))
 7126            {
 07127                inputModifier += " " + userAgentParam;
 7128            }
 7129
 07130            inputModifier = inputModifier.Trim();
 7131
 07132            var refererParam = GetRefererParam(state);
 7133
 07134            if (!string.IsNullOrEmpty(refererParam))
 7135            {
 07136                inputModifier += " " + refererParam;
 7137            }
 7138
 07139            inputModifier = inputModifier.Trim();
 7140
 07141            inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions, segmentContainer);
 07142            inputModifier = inputModifier.Trim();
 7143
 07144            if (state.InputProtocol == MediaProtocol.Rtsp)
 7145            {
 07146                inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
 7147            }
 7148
 07149            if (!string.IsNullOrEmpty(state.InputAudioSync))
 7150            {
 07151                inputModifier += " -async " + state.InputAudioSync;
 7152            }
 7153
 7154            // The -fps_mode option cannot be applied to input
 07155            if (!string.IsNullOrEmpty(state.InputVideoSync) && _mediaEncoder.EncoderVersion < new Version(5, 1))
 7156            {
 07157                inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion);
 7158            }
 7159
 07160            if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp)
 7161            {
 07162                inputModifier += " -re";
 7163            }
 07164            else if (encodingOptions.EnableSegmentDeletion
 07165                && state.VideoStream is not null
 07166                && state.TranscodingType == TranscodingJobType.Hls
 07167                && IsCopyCodec(state.OutputVideoCodec)
 07168                && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateOption)
 7169            {
 7170                // Set an input read rate limit 10x for using SegmentDeletion with stream-copy
 7171                // to prevent ffmpeg from exiting prematurely (due to fast drive)
 07172                inputModifier += " -readrate 10";
 7173            }
 7174
 07175            var flags = new List<string>();
 07176            if (state.IgnoreInputDts)
 7177            {
 07178                flags.Add("+igndts");
 7179            }
 7180
 07181            if (state.IgnoreInputIndex)
 7182            {
 07183                flags.Add("+ignidx");
 7184            }
 7185
 07186            if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
 7187            {
 07188                flags.Add("+genpts");
 7189            }
 7190
 07191            if (state.DiscardCorruptFramesInput)
 7192            {
 07193                flags.Add("+discardcorrupt");
 7194            }
 7195
 07196            if (state.EnableFastSeekInput)
 7197            {
 07198                flags.Add("+fastseek");
 7199            }
 7200
 07201            if (flags.Count > 0)
 7202            {
 07203                inputModifier += " -fflags " + string.Join(string.Empty, flags);
 7204            }
 7205
 07206            if (state.IsVideoRequest)
 7207            {
 07208                if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && encodingOpt
 7209                {
 07210                    var inputFormat = GetInputFormat(state.InputContainer);
 07211                    if (!string.IsNullOrEmpty(inputFormat))
 7212                    {
 07213                        inputModifier += " -f " + inputFormat;
 7214                    }
 7215                }
 7216            }
 7217
 07218            if (state.MediaSource.RequiresLooping)
 7219            {
 07220                inputModifier += " -stream_loop -1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2";
 7221            }
 7222
 07223            return inputModifier;
 7224        }
 7225
 7226        public void AttachMediaSourceInfo(
 7227            EncodingJobInfo state,
 7228            EncodingOptions encodingOptions,
 7229            MediaSourceInfo mediaSource,
 7230            string requestedUrl)
 7231        {
 07232            ArgumentNullException.ThrowIfNull(state);
 7233
 07234            ArgumentNullException.ThrowIfNull(mediaSource);
 7235
 07236            var path = mediaSource.Path;
 07237            var protocol = mediaSource.Protocol;
 7238
 07239            if (!string.IsNullOrEmpty(mediaSource.EncoderPath) && mediaSource.EncoderProtocol.HasValue)
 7240            {
 07241                path = mediaSource.EncoderPath;
 07242                protocol = mediaSource.EncoderProtocol.Value;
 7243            }
 7244
 07245            state.MediaPath = path;
 07246            state.InputProtocol = protocol;
 07247            state.InputContainer = mediaSource.Container;
 07248            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07249            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 7250
 07251            state.IsoType = mediaSource.IsoType;
 7252
 07253            if (mediaSource.Timestamp.HasValue)
 7254            {
 07255                state.InputTimestamp = mediaSource.Timestamp.Value;
 7256            }
 7257
 07258            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07259            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 07260            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
 7261
 07262            if ((state.ReadInputAtNativeFramerate && !state.IsSegmentedLiveStream)
 07263                || (mediaSource.Protocol == MediaProtocol.File
 07264                && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)))
 7265            {
 07266                state.InputVideoSync = "-1";
 07267                state.InputAudioSync = "1";
 7268            }
 7269
 07270            if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase)
 07271                || string.Equals(mediaSource.Container, "asf", StringComparison.OrdinalIgnoreCase))
 7272            {
 7273                // Seeing some stuttering when transcoding wma to audio-only HLS
 07274                state.InputAudioSync = "1";
 7275            }
 7276
 07277            var mediaStreams = mediaSource.MediaStreams;
 7278
 07279            if (state.IsVideoRequest)
 7280            {
 07281                var videoRequest = state.BaseRequest;
 7282
 07283                if (string.IsNullOrEmpty(videoRequest.VideoCodec))
 7284                {
 07285                    if (string.IsNullOrEmpty(requestedUrl))
 7286                    {
 07287                        requestedUrl = "test." + videoRequest.Container;
 7288                    }
 7289
 07290                    videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
 7291                }
 7292
 07293                state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
 07294                state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Su
 07295                state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
 07296                state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
 7297
 07298                if (state.SubtitleStream is not null && !state.SubtitleStream.IsExternal)
 7299                {
 07300                    state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !
 7301                }
 7302
 07303                EnforceResolutionLimit(state);
 7304
 07305                NormalizeSubtitleEmbed(state);
 7306            }
 7307            else
 7308            {
 07309                state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
 7310            }
 7311
 07312            state.MediaSource = mediaSource;
 7313
 07314            var request = state.BaseRequest;
 07315            var supportedAudioCodecs = state.SupportedAudioCodecs;
 07316            if (request is not null && supportedAudioCodecs is not null && supportedAudioCodecs.Length > 0)
 7317            {
 07318                var supportedAudioCodecsList = supportedAudioCodecs.ToList();
 7319
 07320                ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
 7321
 07322                state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray();
 7323
 07324                request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(_mediaEncoder.CanEncodeToAudioCodec)
 07325                    ?? state.SupportedAudioCodecs.FirstOrDefault();
 7326            }
 7327
 07328            var supportedVideoCodecs = state.SupportedVideoCodecs;
 07329            if (request is not null && supportedVideoCodecs is not null && supportedVideoCodecs.Length > 0)
 7330            {
 07331                var supportedVideoCodecsList = supportedVideoCodecs.ToList();
 7332
 07333                ShiftVideoCodecsIfNeeded(supportedVideoCodecsList, encodingOptions);
 7334
 07335                state.SupportedVideoCodecs = supportedVideoCodecsList.ToArray();
 7336
 07337                request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
 7338            }
 07339        }
 7340
 7341        private void ShiftAudioCodecsIfNeeded(List<string> audioCodecs, MediaStream audioStream)
 7342        {
 7343            // No need to shift if there is only one supported audio codec.
 07344            if (audioCodecs.Count < 2)
 7345            {
 07346                return;
 7347            }
 7348
 07349            var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
 07350            var shiftAudioCodecs = new List<string>();
 07351            if (inputChannels >= 6)
 7352            {
 7353                // DTS and TrueHD are not supported by HLS
 7354                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07355                shiftAudioCodecs.Add("dts");
 07356                shiftAudioCodecs.Add("truehd");
 7357            }
 7358            else
 7359            {
 7360                // Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
 7361                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07362                shiftAudioCodecs.Add("ac3");
 07363                shiftAudioCodecs.Add("eac3");
 7364            }
 7365
 07366            if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7367            {
 07368                return;
 7369            }
 7370
 07371            while (shiftAudioCodecs.Contains(audioCodecs[0], StringComparison.OrdinalIgnoreCase))
 7372            {
 07373                var removed = audioCodecs[0];
 07374                audioCodecs.RemoveAt(0);
 07375                audioCodecs.Add(removed);
 7376            }
 07377        }
 7378
 7379        private void ShiftVideoCodecsIfNeeded(List<string> videoCodecs, EncodingOptions encodingOptions)
 7380        {
 7381            // No need to shift if there is only one supported video codec.
 07382            if (videoCodecs.Count < 2)
 7383            {
 07384                return;
 7385            }
 7386
 7387            // Shift codecs to the end of list if it's not allowed.
 07388            var shiftVideoCodecs = new List<string>();
 07389            if (!encodingOptions.AllowHevcEncoding)
 7390            {
 07391                shiftVideoCodecs.Add("hevc");
 07392                shiftVideoCodecs.Add("h265");
 7393            }
 7394
 07395            if (!encodingOptions.AllowAv1Encoding)
 7396            {
 07397                shiftVideoCodecs.Add("av1");
 7398            }
 7399
 07400            if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7401            {
 07402                return;
 7403            }
 7404
 07405            while (shiftVideoCodecs.Contains(videoCodecs[0], StringComparison.OrdinalIgnoreCase))
 7406            {
 07407                var removed = videoCodecs[0];
 07408                videoCodecs.RemoveAt(0);
 07409                videoCodecs.Add(removed);
 7410            }
 07411        }
 7412
 7413        private void NormalizeSubtitleEmbed(EncodingJobInfo state)
 7414        {
 07415            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7416            {
 07417                return;
 7418            }
 7419
 7420            // This is tricky to remux in, after converting to dvdsub it's not positioned correctly
 7421            // Therefore, let's just burn it in
 07422            if (string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 7423            {
 07424                state.SubtitleDeliveryMethod = SubtitleDeliveryMethod.Encode;
 7425            }
 07426        }
 7427
 7428        public string GetSubtitleEmbedArguments(EncodingJobInfo state)
 7429        {
 07430            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7431            {
 07432                return string.Empty;
 7433            }
 7434
 07435            var format = state.SupportedSubtitleCodecs.FirstOrDefault();
 7436            string codec;
 7437
 07438            if (string.IsNullOrEmpty(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.Ordin
 7439            {
 07440                codec = "copy";
 7441            }
 7442            else
 7443            {
 07444                codec = format;
 7445            }
 7446
 07447            return " -codec:s:0 " + codec + " -disposition:s:0 default";
 7448        }
 7449
 7450        public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, Encoder
 7451        {
 7452            // Get the output codec name
 07453            var videoCodec = GetVideoEncoder(state, encodingOptions);
 7454
 07455            var format = string.Empty;
 07456            var keyFrame = string.Empty;
 07457            var outputPath = state.OutputFilePath;
 7458
 07459            if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase)
 07460                && state.BaseRequest.Context == EncodingContext.Streaming)
 7461            {
 7462                // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
 07463                format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov";
 7464            }
 7465
 07466            var threads = GetNumberOfThreads(state, encodingOptions, videoCodec);
 7467
 07468            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7469
 07470            return string.Format(
 07471                CultureInfo.InvariantCulture,
 07472                "{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
 07473                inputModifier,
 07474                GetInputArgument(state, encodingOptions, null),
 07475                keyFrame,
 07476                GetMapArgs(state),
 07477                GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset),
 07478                threads,
 07479                GetProgressiveVideoAudioArguments(state, encodingOptions),
 07480                GetSubtitleEmbedArguments(state),
 07481                format,
 07482                outputPath).Trim();
 7483        }
 7484
 7485        public string GetOutputFFlags(EncodingJobInfo state)
 7486        {
 07487            var flags = new List<string>();
 07488            if (state.GenPtsOutput)
 7489            {
 07490                flags.Add("+genpts");
 7491            }
 7492
 07493            if (flags.Count > 0)
 7494            {
 07495                return " -fflags " + string.Join(string.Empty, flags);
 7496            }
 7497
 07498            return string.Empty;
 7499        }
 7500
 7501        public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoC
 7502        {
 07503            var args = "-codec:v:0 " + videoCodec;
 7504
 07505            if (state.BaseRequest.EnableMpegtsM2TsMode)
 7506            {
 07507                args += " -mpegts_m2ts_mode 1";
 7508            }
 7509
 07510            if (IsCopyCodec(videoCodec))
 7511            {
 07512                if (state.VideoStream is not null
 07513                    && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
 07514                    && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
 7515                {
 07516                    string bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Video);
 07517                    if (!string.IsNullOrEmpty(bitStreamArgs))
 7518                    {
 07519                        args += " " + bitStreamArgs;
 7520                    }
 7521                }
 7522
 07523                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7524                {
 07525                    args += " -copyts -avoid_negative_ts disabled -start_at_zero";
 7526                }
 7527
 07528                if (!state.RunTimeTicks.HasValue)
 7529                {
 07530                    args += " -fflags +genpts";
 7531                }
 7532            }
 7533            else
 7534            {
 07535                var keyFrameArg = string.Format(
 07536                    CultureInfo.InvariantCulture,
 07537                    " -force_key_frames \"expr:gte(t,n_forced*{0})\"",
 07538                    5);
 7539
 07540                args += keyFrameArg;
 7541
 07542                var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream &&
 7543
 07544                var hasCopyTs = false;
 7545
 7546                // video processing filters.
 07547                var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
 7548
 07549                var negativeMapArgs = GetNegativeMapArgsByFilters(state, videoProcessParam);
 7550
 07551                args = negativeMapArgs + args + videoProcessParam;
 7552
 07553                hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
 7554
 07555                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7556                {
 07557                    if (!hasCopyTs)
 7558                    {
 07559                        args += " -copyts";
 7560                    }
 7561
 07562                    args += " -avoid_negative_ts disabled";
 7563
 07564                    if (!(state.SubtitleStream is not null && state.SubtitleStream.IsExternal && !state.SubtitleStream.I
 7565                    {
 07566                        args += " -start_at_zero";
 7567                    }
 7568                }
 7569
 07570                var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset);
 7571
 07572                if (!string.IsNullOrEmpty(qualityParam))
 7573                {
 07574                    args += " " + qualityParam.Trim();
 7575                }
 7576            }
 7577
 07578            if (!string.IsNullOrEmpty(state.OutputVideoSync))
 7579            {
 07580                args += GetVideoSyncOption(state.OutputVideoSync, _mediaEncoder.EncoderVersion);
 7581            }
 7582
 07583            args += GetOutputFFlags(state);
 7584
 07585            return args;
 7586        }
 7587
 7588        public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions)
 7589        {
 7590            // If the video doesn't have an audio stream, return a default.
 07591            if (state.AudioStream is null && state.VideoStream is not null)
 7592            {
 07593                return string.Empty;
 7594            }
 7595
 7596            // Get the output codec name
 07597            var codec = GetAudioEncoder(state);
 7598
 07599            var args = "-codec:a:0 " + codec;
 7600
 07601            if (IsCopyCodec(codec))
 7602            {
 07603                return args;
 7604            }
 7605
 07606            var channels = state.OutputAudioChannels;
 7607
 07608            var useDownMixAlgorithm = state.AudioStream is not null
 07609                                      && DownMixAlgorithmsHelper.AlgorithmFilterStrings.ContainsKey((encodingOptions.Dow
 7610
 07611            if (channels.HasValue && !useDownMixAlgorithm)
 7612            {
 07613                args += " -ac " + channels.Value;
 7614            }
 7615
 07616            var bitrate = state.OutputAudioBitrate;
 07617            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
 7618            {
 07619                var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2);
 07620                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7621                {
 07622                    args += vbrParam;
 7623                }
 7624                else
 7625                {
 07626                    args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
 7627                }
 7628            }
 7629
 07630            if (state.OutputAudioSampleRate.HasValue)
 7631            {
 07632                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
 7633            }
 7634
 07635            args += GetAudioFilterParam(state, encodingOptions);
 7636
 07637            return args;
 7638        }
 7639
 7640        public string GetProgressiveAudioFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string 
 7641        {
 07642            var audioTranscodeParams = new List<string>();
 7643
 07644            var bitrate = state.OutputAudioBitrate;
 07645            var channels = state.OutputAudioChannels;
 07646            var outputCodec = state.OutputAudioCodec;
 7647
 07648            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
 7649            {
 07650                var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2);
 07651                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7652                {
 07653                    audioTranscodeParams.Add(vbrParam);
 7654                }
 7655                else
 7656                {
 07657                    audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
 7658                }
 7659            }
 7660
 07661            if (channels.HasValue)
 7662            {
 07663                audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)
 7664            }
 7665
 07666            if (!string.IsNullOrEmpty(outputCodec))
 7667            {
 07668                audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
 7669            }
 7670
 07671            if (GetAudioEncoder(state).StartsWith("pcm_", StringComparison.Ordinal))
 7672            {
 07673                audioTranscodeParams.Add(string.Concat("-f ", GetAudioEncoder(state).AsSpan(4)));
 07674                audioTranscodeParams.Add("-ar " + state.BaseRequest.AudioBitRate);
 7675            }
 7676
 07677            if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
 7678            {
 7679                // opus only supports specific sampling rates
 07680                var sampleRate = state.OutputAudioSampleRate;
 07681                if (sampleRate.HasValue)
 7682                {
 07683                    var sampleRateValue = sampleRate.Value switch
 07684                    {
 07685                        <= 8000 => 8000,
 07686                        <= 12000 => 12000,
 07687                        <= 16000 => 16000,
 07688                        <= 24000 => 24000,
 07689                        _ => 48000
 07690                    };
 7691
 07692                    audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
 7693                }
 7694            }
 7695
 7696            // Copy the movflags from GetProgressiveVideoFullCommandLine
 7697            // See #9248 and the associated PR for why this is needed
 07698            if (_mp4ContainerNames.Contains(state.OutputContainer))
 7699            {
 07700                audioTranscodeParams.Add("-movflags empty_moov+delay_moov");
 7701            }
 7702
 07703            var threads = GetNumberOfThreads(state, encodingOptions, null);
 7704
 07705            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7706
 07707            return string.Format(
 07708                CultureInfo.InvariantCulture,
 07709                "{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"",
 07710                inputModifier,
 07711                GetInputArgument(state, encodingOptions, null),
 07712                threads,
 07713                " -vn",
 07714                string.Join(' ', audioTranscodeParams),
 07715                outputPath,
 07716                string.Empty,
 07717                string.Empty,
 07718                string.Empty).Trim();
 7719        }
 7720
 7721        public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
 7722        {
 07723            var index = 0;
 07724            var length = mediaStreams.Count;
 7725
 07726            for (var i = 0; i < length; i++)
 7727            {
 07728                var currentMediaStream = mediaStreams[i];
 07729                if (currentMediaStream == streamToFind)
 7730                {
 07731                    return index;
 7732                }
 7733
 07734                if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
 7735                {
 07736                    index++;
 7737                }
 7738            }
 7739
 07740            return -1;
 7741        }
 7742
 7743        public static bool IsCopyCodec(string codec)
 7744        {
 07745            return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
 7746        }
 7747
 7748        private static bool ShouldEncodeSubtitle(EncodingJobInfo state)
 7749        {
 07750            return state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
 07751                   || (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec));
 7752        }
 7753
 7754        public static string GetVideoSyncOption(string videoSync, Version encoderVersion)
 7755        {
 07756            if (string.IsNullOrEmpty(videoSync))
 7757            {
 07758                return string.Empty;
 7759            }
 7760
 07761            if (encoderVersion >= new Version(5, 1))
 7762            {
 07763                if (int.TryParse(videoSync, CultureInfo.InvariantCulture, out var vsync))
 7764                {
 07765                    return vsync switch
 07766                    {
 07767                        -1 => " -fps_mode auto",
 07768                        0 => " -fps_mode passthrough",
 07769                        1 => " -fps_mode cfr",
 07770                        2 => " -fps_mode vfr",
 07771                        _ => string.Empty
 07772                    };
 7773                }
 7774
 07775                return string.Empty;
 7776            }
 7777
 7778            // -vsync is deprecated in FFmpeg 5.1 and will be removed in the future.
 07779            return $" -vsync {videoSync}";
 7780        }
 7781    }
 7782}

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)