< 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: 3720
Coverable lines: 3746
Total lines: 7851
Line coverage: 0.6%
Branch coverage
0%
Covered branches: 0
Total branches: 3735
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 12/6/2025 - 12:11:15 AM Line coverage: 0.6% (26/3727) Branch coverage: 0% (0/3713) Total lines: 779812/29/2025 - 12:13:19 AM Line coverage: 0.6% (26/3732) Branch coverage: 0% (0/3721) Total lines: 78131/19/2026 - 12:13:54 AM Line coverage: 0.6% (26/3734) Branch coverage: 0% (0/3729) Total lines: 78183/14/2026 - 12:13:58 AM Line coverage: 0.6% (26/3746) Branch coverage: 0% (0/3735) Total lines: 7851 12/6/2025 - 12:11:15 AM Line coverage: 0.6% (26/3727) Branch coverage: 0% (0/3713) Total lines: 779812/29/2025 - 12:13:19 AM Line coverage: 0.6% (26/3732) Branch coverage: 0% (0/3721) Total lines: 78131/19/2026 - 12:13:54 AM Line coverage: 0.6% (26/3734) Branch coverage: 0% (0/3729) Total lines: 78183/14/2026 - 12:13:58 AM Line coverage: 0.6% (26/3746) Branch coverage: 0% (0/3735) Total lines: 7851

Coverage delta

Coverage delta 1 -1

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%210140%
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%1482380%
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%175561320%
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%160021260%
GetVideoProcessingFilterParam(...)0%2756520%
GetOverwriteColorPropertiesParam(...)0%2040%
GetInputHdrParam(...)0%620%
GetOutputSdrParam(...)0%2040%
GetVideoColorBitDepth(...)0%600240%
GetHardwareVideoDecoder(...)0%3422580%
GetHwDecoderName(...)0%1190340%
GetHwaccelType(...)0%150061220%
GetQsvHwVidDecoder(...)0%3192560%
GetNvdecVidDecoder(...)0%2162460%
GetAmfVidDecoder(...)0%1056320%
GetVaapiVidDecoder(...)0%2756520%
GetVideotoolboxVidDecoder(...)0%2352480%
GetRkmppVidDecoder(...)0%2756520%
GetNumberOfThreads(...)0%4260%
TryStreamCopy(...)0%600240%
GetFfmpegAnalyzeDurationArg(...)0%4260%
GetFfmpegProbesizeArg()0%620%
GetInputModifier(...)0%3192560%
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 enable VA-API MJPEG encoder on Intel iHD driver.
 234                // Legacy platforms supported ONLY by i965 do not support MJPEG encoder.
 0235                if (hwType == HardwareAccelerationType.vaapi
 0236                    && !_mediaEncoder.IsVaapiDeviceInteliHD)
 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                // Use analyzeduration also for subtitle streams to improve resolution detection  with streams inside MK
 01271                var analyzeDurationArgument = GetFfmpegAnalyzeDurationArg(state);
 01272                if (!string.IsNullOrEmpty(analyzeDurationArgument))
 1273                {
 01274                    arg.Append(' ').Append(analyzeDurationArgument);
 1275                }
 1276
 1277                // Apply probesize, too, if configured
 01278                var ffmpegProbeSizeArgument = GetFfmpegProbesizeArg();
 01279                if (!string.IsNullOrEmpty(ffmpegProbeSizeArgument))
 1280                {
 01281                    arg.Append(' ').Append(ffmpegProbeSizeArgument);
 1282                }
 1283
 1284                // Also seek the external subtitles stream.
 01285                var seekSubParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01286                if (!string.IsNullOrEmpty(seekSubParam))
 1287                {
 01288                    arg.Append(' ').Append(seekSubParam);
 1289                }
 1290
 01291                if (!string.IsNullOrEmpty(canvasArgs))
 1292                {
 01293                    arg.Append(canvasArgs);
 1294                }
 1295
 01296                arg.Append(" -i file:\"").Append(subtitlePath).Append('\"');
 1297            }
 1298
 01299            if (state.AudioStream is not null && state.AudioStream.IsExternal)
 1300            {
 1301                // Also seek the external audio stream.
 01302                var seekAudioParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01303                if (!string.IsNullOrEmpty(seekAudioParam))
 1304                {
 01305                    arg.Append(' ').Append(seekAudioParam);
 1306                }
 1307
 01308                arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
 1309            }
 1310
 1311            // Disable auto inserted SW scaler for HW decoders in case of changed resolution.
 01312            var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
 01313            if (!isSwDecoder)
 1314            {
 01315                arg.Append(" -noautoscale");
 1316            }
 1317
 01318            return arg.ToString();
 1319        }
 1320
 1321        /// <summary>
 1322        /// Determines whether the specified stream is H264.
 1323        /// </summary>
 1324        /// <param name="stream">The stream.</param>
 1325        /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
 1326        public static bool IsH264(MediaStream stream)
 1327        {
 01328            var codec = stream.Codec ?? string.Empty;
 1329
 01330            return codec.Contains("264", StringComparison.OrdinalIgnoreCase)
 01331                    || codec.Contains("avc", StringComparison.OrdinalIgnoreCase);
 1332        }
 1333
 1334        public static bool IsH265(MediaStream stream)
 1335        {
 01336            var codec = stream.Codec ?? string.Empty;
 1337
 01338            return codec.Contains("265", StringComparison.OrdinalIgnoreCase)
 01339                || codec.Contains("hevc", StringComparison.OrdinalIgnoreCase);
 1340        }
 1341
 1342        public static bool IsAv1(MediaStream stream)
 1343        {
 01344            var codec = stream.Codec ?? string.Empty;
 1345
 01346            return codec.Contains("av1", StringComparison.OrdinalIgnoreCase);
 1347        }
 1348
 1349        public static bool IsAAC(MediaStream stream)
 1350        {
 01351            var codec = stream.Codec ?? string.Empty;
 1352
 01353            return codec.Contains("aac", StringComparison.OrdinalIgnoreCase);
 1354        }
 1355
 1356        public static bool IsDoviWithHdr10Bl(MediaStream stream)
 1357        {
 01358            var rangeType = stream?.VideoRangeType;
 1359
 01360            return rangeType is VideoRangeType.DOVIWithHDR10
 01361                or VideoRangeType.DOVIWithEL
 01362                or VideoRangeType.DOVIWithHDR10Plus
 01363                or VideoRangeType.DOVIWithELHDR10Plus
 01364                or VideoRangeType.DOVIInvalid;
 1365        }
 1366
 1367        public static bool IsDovi(MediaStream stream)
 1368        {
 01369            var rangeType = stream?.VideoRangeType;
 1370
 01371            return IsDoviWithHdr10Bl(stream)
 01372                   || (rangeType is VideoRangeType.DOVI
 01373                       or VideoRangeType.DOVIWithHLG
 01374                       or VideoRangeType.DOVIWithSDR);
 1375        }
 1376
 1377        public static bool IsHdr10Plus(MediaStream stream)
 1378        {
 01379            var rangeType = stream?.VideoRangeType;
 1380
 01381            return rangeType is VideoRangeType.HDR10Plus
 01382                       or VideoRangeType.DOVIWithHDR10Plus
 01383                       or VideoRangeType.DOVIWithELHDR10Plus;
 1384        }
 1385
 1386        /// <summary>
 1387        /// Check if dynamic HDR metadata should be removed during stream copy.
 1388        /// Please note this check assumes the range check has already been done
 1389        /// and trivial fallbacks like HDR10+ to HDR10, DOVIWithHDR10 to HDR10 is already checked.
 1390        /// </summary>
 1391        private static DynamicHdrMetadataRemovalPlan ShouldRemoveDynamicHdrMetadata(EncodingJobInfo state)
 1392        {
 01393            var videoStream = state.VideoStream;
 01394            if (videoStream.VideoRange is not VideoRange.HDR)
 1395            {
 01396                return DynamicHdrMetadataRemovalPlan.None;
 1397            }
 1398
 01399            var requestedRangeTypes = state.GetRequestedRangeTypes(state.VideoStream.Codec);
 01400            if (requestedRangeTypes.Length == 0)
 1401            {
 01402                return DynamicHdrMetadataRemovalPlan.None;
 1403            }
 1404
 01405            var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ordinal
 01406            var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIg
 01407            var requestHasDOVIwithEL = requestedRangeTypes.Contains(VideoRangeType.DOVIWithEL.ToString(), StringComparis
 01408            var requestHasDOVIwithELHDR10plus = requestedRangeTypes.Contains(VideoRangeType.DOVIWithELHDR10Plus.ToString
 1409
 01410            var shouldRemoveHdr10Plus = false;
 1411            // Case 1: Client supports HDR10, does not support DOVI with EL but EL presets
 01412            var shouldRemoveDovi = (!requestHasDOVIwithEL && requestHasHDR10) && videoStream.VideoRangeType == VideoRang
 1413
 1414            // Case 2: Client supports DOVI, does not support broken DOVI config
 1415            // Client does not report DOVI support should be allowed to copy bad data for remuxing as HDR10 players woul
 01416            shouldRemoveDovi = shouldRemoveDovi || (requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVII
 1417
 1418            // Special case: we have a video with both EL and HDR10+
 1419            // If the client supports EL but not in the case of coexistence with HDR10+, remove HDR10+ for compatibility
 1420            // Otherwise, remove DOVI if the client is not a DOVI player
 01421            if (videoStream.VideoRangeType == VideoRangeType.DOVIWithELHDR10Plus)
 1422            {
 01423                shouldRemoveHdr10Plus = requestHasDOVIwithEL && !requestHasDOVIwithELHDR10plus;
 01424                shouldRemoveDovi = shouldRemoveDovi || !shouldRemoveHdr10Plus;
 1425            }
 1426
 01427            if (shouldRemoveDovi)
 1428            {
 01429                return DynamicHdrMetadataRemovalPlan.RemoveDovi;
 1430            }
 1431
 1432            // If the client is a Dolby Vision Player, remove the HDR10+ metadata to avoid playback issues
 01433            shouldRemoveHdr10Plus = shouldRemoveHdr10Plus || (requestHasDOVI && videoStream.VideoRangeType == VideoRange
 01434            return shouldRemoveHdr10Plus ? DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus : DynamicHdrMetadataRemovalPlan
 1435        }
 1436
 1437        private bool CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan plan, MediaStream videoStream)
 1438        {
 01439            return plan switch
 01440            {
 01441                DynamicHdrMetadataRemovalPlan.RemoveDovi => _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFil
 01442                                                            || (IsH265(videoStream) && _mediaEncoder.SupportsBitStreamFi
 01443                                                            || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStreamFil
 01444                DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus => (IsH265(videoStream) && _mediaEncoder.SupportsBitStream
 01445                                                                 || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStre
 01446                _ => true,
 01447            };
 1448        }
 1449
 1450        public bool IsDoviRemoved(EncodingJobInfo state)
 1451        {
 01452            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01453                                              && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Remove
 1454        }
 1455
 1456        public bool IsHdr10PlusRemoved(EncodingJobInfo state)
 1457        {
 01458            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01459                                                  && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Re
 1460        }
 1461
 1462        public string GetBitStreamArgs(EncodingJobInfo state, MediaStreamType streamType)
 1463        {
 01464            if (state is null)
 1465            {
 01466                return null;
 1467            }
 1468
 01469            var stream = streamType switch
 01470            {
 01471                MediaStreamType.Audio => state.AudioStream,
 01472                MediaStreamType.Video => state.VideoStream,
 01473                _ => state.VideoStream
 01474            };
 1475            // TODO This is auto inserted into the mpegts mux so it might not be needed.
 1476            // https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
 01477            if (IsH264(stream))
 1478            {
 01479                return "-bsf:v h264_mp4toannexb";
 1480            }
 1481
 01482            if (IsAAC(stream))
 1483            {
 1484                // Convert adts header(mpegts) to asc header(mp4).
 01485                return "-bsf:a aac_adtstoasc";
 1486            }
 1487
 01488            if (IsH265(stream))
 1489            {
 01490                var filter = "-bsf:v hevc_mp4toannexb";
 1491
 1492                // The following checks are not complete because the copy would be rejected
 1493                // if the encoder cannot remove required metadata.
 1494                // And if bsf is used, we must already be using copy codec.
 01495                switch (ShouldRemoveDynamicHdrMetadata(state))
 1496                {
 1497                    default:
 1498                    case DynamicHdrMetadataRemovalPlan.None:
 1499                        break;
 1500                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01501                        filter += _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.HevcMetadata
 01502                            ? ",hevc_metadata=remove_dovi=1"
 01503                            : ",dovi_rpu=strip=1";
 01504                        break;
 1505                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01506                        filter += ",hevc_metadata=remove_hdr10plus=1";
 1507                        break;
 1508                }
 1509
 01510                return filter;
 1511            }
 1512
 01513            if (IsAv1(stream))
 1514            {
 01515                switch (ShouldRemoveDynamicHdrMetadata(state))
 1516                {
 1517                    default:
 1518                    case DynamicHdrMetadataRemovalPlan.None:
 01519                        return null;
 1520                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01521                        return _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.Av1MetadataRemo
 01522                            ? "-bsf:v av1_metadata=remove_dovi=1"
 01523                            : "-bsf:v dovi_rpu=strip=1";
 1524                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01525                        return "-bsf:v av1_metadata=remove_hdr10plus=1";
 1526                }
 1527            }
 1528
 01529            return null;
 1530        }
 1531
 1532        public string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceConta
 1533        {
 01534            var bitStreamArgs = string.Empty;
 01535            var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
 1536
 1537            // Apply aac_adtstoasc bitstream filter when media source is in mpegts.
 01538            if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)
 01539                && (string.Equals(mediaSourceContainer, "ts", StringComparison.OrdinalIgnoreCase)
 01540                    || string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase)
 01541                    || string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase)))
 1542            {
 01543                bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Audio);
 01544                bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs;
 1545            }
 1546
 01547            return bitStreamArgs;
 1548        }
 1549
 1550        public static string GetSegmentFileExtension(string segmentContainer)
 1551        {
 01552            if (!string.IsNullOrWhiteSpace(segmentContainer))
 1553            {
 01554                return "." + segmentContainer;
 1555            }
 1556
 01557            return ".ts";
 1558        }
 1559
 1560        private string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
 1561        {
 01562            if (state.OutputVideoBitrate is null)
 1563            {
 01564                return string.Empty;
 1565            }
 1566
 01567            int bitrate = state.OutputVideoBitrate.Value;
 1568
 1569            // Bit rate under 1000k is not allowed in h264_qsv
 01570            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 1571            {
 01572                bitrate = Math.Max(bitrate, 1000);
 1573            }
 1574
 1575            // Currently use the same buffer size for all encoders
 01576            int bufsize = bitrate * 2;
 1577
 01578            if (string.Equals(videoCodec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1579            {
 01580                return FormattableString.Invariant($" -b:v {bitrate} -bufsize {bufsize}");
 1581            }
 1582
 01583            if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)
 01584                || string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
 1585            {
 01586                return FormattableString.Invariant($" -maxrate {bitrate} -bufsize {bufsize}");
 1587            }
 1588
 01589            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01590                || string.Equals(videoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 01591                || string.Equals(videoCodec, "av1_qsv", StringComparison.OrdinalIgnoreCase))
 1592            {
 1593                // TODO: probe QSV encoders' capabilities and enable more tuning options
 1594                // See also https://github.com/intel/media-delivery/blob/master/doc/quality.rst
 1595
 1596                // Enable MacroBlock level bitrate control for better subjective visual quality
 01597                var mbbrcOpt = string.Empty;
 01598                if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01599                    || string.Equals(videoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 1600                {
 01601                    mbbrcOpt = " -mbbrc 1";
 1602                }
 1603
 1604                // Set (maxrate == bitrate + 1) to trigger VBR for better bitrate allocation
 1605                // Set (rc_init_occupancy == 2 * bitrate) and (bufsize == 4 * bitrate) to deal with drastic scene change
 01606                return FormattableString.Invariant($"{mbbrcOpt} -b:v {bitrate} -maxrate {bitrate + 1} -rc_init_occupancy
 1607            }
 1608
 01609            if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01610                || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01611                || string.Equals(videoCodec, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1612            {
 1613                // Override the too high default qmin 18 in transcoding preset
 01614                return FormattableString.Invariant($" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsiz
 1615            }
 1616
 01617            if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01618                || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01619                || string.Equals(videoCodec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1620            {
 1621                // VBR in i965 driver may result in pixelated output.
 01622                if (_mediaEncoder.IsVaapiDeviceInteli965)
 1623                {
 01624                    return FormattableString.Invariant($" -rc_mode CBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsi
 1625                }
 1626
 01627                return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"
 1628            }
 1629
 01630            if (string.Equals(videoCodec, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 01631                || string.Equals(videoCodec, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase))
 1632            {
 1633                // The `maxrate` and `bufsize` options can potentially lead to performance regression
 1634                // and even encoder hangs, especially when the value is very high.
 01635                return FormattableString.Invariant($" -b:v {bitrate} -qmin -1 -qmax -1");
 1636            }
 1637
 01638            return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
 1639        }
 1640
 1641        private string GetEncoderParam(EncoderPreset? preset, EncoderPreset defaultPreset, EncodingOptions encodingOptio
 1642        {
 01643            var param = string.Empty;
 01644            var encoderPreset = preset ?? defaultPreset;
 01645            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
 1646            {
 01647                var presetString = encoderPreset switch
 01648                {
 01649                    EncoderPreset.auto => EncoderPreset.veryfast.ToString().ToLowerInvariant(),
 01650                    _ => encoderPreset.ToString().ToLowerInvariant()
 01651                };
 1652
 01653                param += " -preset " + presetString;
 1654
 01655                int encodeCrf = encodingOptions.H264Crf;
 01656                if (isLibX265)
 1657                {
 01658                    encodeCrf = encodingOptions.H265Crf;
 1659                }
 1660
 01661                if (encodeCrf >= 0 && encodeCrf <= 51)
 1662                {
 01663                    param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture);
 1664                }
 1665                else
 1666                {
 01667                    string defaultCrf = "23";
 01668                    if (isLibX265)
 1669                    {
 01670                        defaultCrf = "28";
 1671                    }
 1672
 01673                    param += " -crf " + defaultCrf;
 1674                }
 1675            }
 01676            else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1677            {
 1678                // Default to use the recommended preset 10.
 1679                // Omit presets < 5, which are too slow for on the fly encoding.
 1680                // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md
 01681                param += encoderPreset switch
 01682                {
 01683                    EncoderPreset.veryslow => " -preset 5",
 01684                    EncoderPreset.slower => " -preset 6",
 01685                    EncoderPreset.slow => " -preset 7",
 01686                    EncoderPreset.medium => " -preset 8",
 01687                    EncoderPreset.fast => " -preset 9",
 01688                    EncoderPreset.faster => " -preset 10",
 01689                    EncoderPreset.veryfast => " -preset 11",
 01690                    EncoderPreset.superfast => " -preset 12",
 01691                    EncoderPreset.ultrafast => " -preset 13",
 01692                    _ => " -preset 10"
 01693                };
 1694            }
 01695            else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01696                     || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01697                     || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1698            {
 1699                // -compression_level is not reliable on AMD.
 01700                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 1701                {
 01702                    param += encoderPreset switch
 01703                    {
 01704                        EncoderPreset.veryslow => " -compression_level 1",
 01705                        EncoderPreset.slower => " -compression_level 2",
 01706                        EncoderPreset.slow => " -compression_level 3",
 01707                        EncoderPreset.medium => " -compression_level 4",
 01708                        EncoderPreset.fast => " -compression_level 5",
 01709                        EncoderPreset.faster => " -compression_level 6",
 01710                        EncoderPreset.veryfast => " -compression_level 7",
 01711                        EncoderPreset.superfast => " -compression_level 7",
 01712                        EncoderPreset.ultrafast => " -compression_level 7",
 01713                        _ => string.Empty
 01714                    };
 1715                }
 1716            }
 01717            else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv)
 01718                     || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv)
 01719                     || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv)
 1720            {
 01721                EncoderPreset[] valid_presets = [EncoderPreset.veryslow, EncoderPreset.slower, EncoderPreset.slow, Encod
 1722
 01723                param += " -preset " + (valid_presets.Contains(encoderPreset) ? encoderPreset : EncoderPreset.veryfast).
 1724            }
 01725            else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
 01726                        || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_n
 01727                        || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase) // av1 (av1_nven
 01728            )
 1729            {
 01730                param += encoderPreset switch
 01731                {
 01732                        EncoderPreset.veryslow => " -preset p7",
 01733                        EncoderPreset.slower => " -preset p6",
 01734                        EncoderPreset.slow => " -preset p5",
 01735                        EncoderPreset.medium => " -preset p4",
 01736                        EncoderPreset.fast => " -preset p3",
 01737                        EncoderPreset.faster => " -preset p2",
 01738                        _ => " -preset p1"
 01739                };
 1740            }
 01741            else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf)
 01742                        || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf
 01743                        || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase) // av1 (av1_amf)
 01744            )
 1745            {
 01746                param += encoderPreset switch
 01747                {
 01748                        EncoderPreset.veryslow => " -quality quality",
 01749                        EncoderPreset.slower => " -quality quality",
 01750                        EncoderPreset.slow => " -quality quality",
 01751                        EncoderPreset.medium => " -quality balanced",
 01752                        _ => " -quality speed"
 01753                };
 1754
 01755                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01756                    || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1757                {
 01758                    param += " -header_insertion_mode gop";
 1759                }
 1760
 01761                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
 1762                {
 01763                    param += " -gops_per_idr 1";
 1764                }
 1765            }
 01766            else if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase) // h264 (h264_
 01767                        || string.Equals(videoEncoder, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase) // hevc 
 01768            )
 1769            {
 01770                param += encoderPreset switch
 01771                {
 01772                        EncoderPreset.veryslow => " -prio_speed 0",
 01773                        EncoderPreset.slower => " -prio_speed 0",
 01774                        EncoderPreset.slow => " -prio_speed 0",
 01775                        EncoderPreset.medium => " -prio_speed 0",
 01776                        _ => " -prio_speed 1"
 01777                };
 1778            }
 1779
 01780            return param;
 1781        }
 1782
 1783        public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
 1784        {
 01785            if (double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel))
 1786            {
 01787                if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase))
 1788                {
 1789                    // Transcode to level 5.3 (15) and lower for maximum compatibility.
 1790                    // https://en.wikipedia.org/wiki/AV1#Levels
 01791                    if (requestLevel < 0 || requestLevel >= 15)
 1792                    {
 01793                        return "15";
 1794                    }
 1795                }
 01796                else if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 01797                         || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
 1798                {
 1799                    // Transcode to level 5.0 and lower for maximum compatibility.
 1800                    // Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it.
 1801                    // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
 1802                    // MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880.
 01803                    if (requestLevel < 0 || requestLevel >= 150)
 1804                    {
 01805                        return "150";
 1806                    }
 1807                }
 01808                else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
 1809                {
 1810                    // Transcode to level 5.1 and lower for maximum compatibility.
 1811                    // h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
 1812                    // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
 01813                    if (requestLevel < 0 || requestLevel >= 51)
 1814                    {
 01815                        return "51";
 1816                    }
 1817                }
 1818            }
 1819
 01820            return level;
 1821        }
 1822
 1823        /// <summary>
 1824        /// Gets the text subtitle param.
 1825        /// </summary>
 1826        /// <param name="state">The state.</param>
 1827        /// <param name="enableAlpha">Enable alpha processing.</param>
 1828        /// <param name="enableSub2video">Enable sub2video mode.</param>
 1829        /// <returns>System.String.</returns>
 1830        public string GetTextSubtitlesFilter(EncodingJobInfo state, bool enableAlpha, bool enableSub2video)
 1831        {
 01832            var seconds = Math.Round(TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds);
 1833
 1834            // hls always copies timestamps
 01835            var setPtsParam = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive
 01836                ? string.Empty
 01837                : string.Format(CultureInfo.InvariantCulture, ",setpts=PTS -{0}/TB", seconds);
 1838
 01839            var alphaParam = enableAlpha ? ":alpha=1" : string.Empty;
 01840            var sub2videoParam = enableSub2video ? ":sub2video=1" : string.Empty;
 1841
 01842            var fontPath = _pathManager.GetAttachmentFolderPath(state.MediaSource.Id);
 01843            var fontParam = string.Format(
 01844                CultureInfo.InvariantCulture,
 01845                ":fontsdir='{0}'",
 01846                _mediaEncoder.EscapeSubtitleFilterPath(fontPath));
 1847
 01848            if (state.SubtitleStream.IsExternal)
 1849            {
 01850                var charsetParam = string.Empty;
 1851
 01852                if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
 1853                {
 01854                    var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
 01855                            state.SubtitleStream,
 01856                            state.SubtitleStream.Language,
 01857                            state.MediaSource,
 01858                            CancellationToken.None).GetAwaiter().GetResult();
 1859
 01860                    if (!string.IsNullOrEmpty(charenc))
 1861                    {
 01862                        charsetParam = ":charenc=" + charenc;
 1863                    }
 1864                }
 1865
 01866                return string.Format(
 01867                    CultureInfo.InvariantCulture,
 01868                    "subtitles=f='{0}'{1}{2}{3}{4}{5}",
 01869                    _mediaEncoder.EscapeSubtitleFilterPath(state.SubtitleStream.Path),
 01870                    charsetParam,
 01871                    alphaParam,
 01872                    sub2videoParam,
 01873                    fontParam,
 01874                    setPtsParam);
 1875            }
 1876
 01877            var subtitlePath = _subtitleEncoder.GetSubtitleFilePath(
 01878                    state.SubtitleStream,
 01879                    state.MediaSource,
 01880                    CancellationToken.None).GetAwaiter().GetResult();
 1881
 01882            return string.Format(
 01883                CultureInfo.InvariantCulture,
 01884                "subtitles=f='{0}'{1}{2}{3}{4}",
 01885                _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
 01886                alphaParam,
 01887                sub2videoParam,
 01888                fontParam,
 01889                setPtsParam);
 1890        }
 1891
 1892        public double? GetFramerateParam(EncodingJobInfo state)
 1893        {
 01894            var request = state.BaseRequest;
 1895
 01896            if (request.Framerate.HasValue)
 1897            {
 01898                return request.Framerate.Value;
 1899            }
 1900
 01901            var maxrate = request.MaxFramerate;
 1902
 01903            if (maxrate.HasValue && state.VideoStream is not null)
 1904            {
 01905                var contentRate = state.VideoStream.ReferenceFrameRate;
 1906
 01907                if (contentRate.HasValue && contentRate.Value > maxrate.Value)
 1908                {
 01909                    return maxrate;
 1910                }
 1911            }
 1912
 01913            return null;
 1914        }
 1915
 1916        public string GetHlsVideoKeyFrameArguments(
 1917            EncodingJobInfo state,
 1918            string codec,
 1919            int segmentLength,
 1920            bool isEventPlaylist,
 1921            int? startNumber)
 1922        {
 01923            var args = string.Empty;
 01924            var gopArg = string.Empty;
 1925
 01926            var keyFrameArg = string.Format(
 01927                CultureInfo.InvariantCulture,
 01928                " -force_key_frames:0 \"expr:gte(t,n_forced*{0})\"",
 01929                segmentLength);
 1930
 01931            var framerate = state.VideoStream?.RealFrameRate;
 01932            if (framerate.HasValue)
 1933            {
 1934                // This is to make sure keyframe interval is limited to our segment,
 1935                // as forcing keyframes is not enough.
 1936                // Example: we encoded half of desired length, then codec detected
 1937                // scene cut and inserted a keyframe; next forced keyframe would
 1938                // be created outside of segment, which breaks seeking.
 01939                gopArg = string.Format(
 01940                    CultureInfo.InvariantCulture,
 01941                    " -g:v:0 {0} -keyint_min:v:0 {0}",
 01942                    Math.Ceiling(segmentLength * framerate.Value));
 1943            }
 1944
 1945            // Unable to force key frames using these encoders, set key frames by GOP.
 01946            if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01947                || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 01948                || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01949                || string.Equals(codec, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 01950                || string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 01951                || string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 01952                || string.Equals(codec, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase)
 01953                || string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 01954                || string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 01955                || string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase)
 01956                || string.Equals(codec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1957            {
 01958                args += gopArg;
 1959            }
 01960            else if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase)
 01961                     || string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)
 01962                     || string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01963                     || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01964                     || string.Equals(codec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1965            {
 01966                args += keyFrameArg;
 1967
 1968                // prevent the libx264 from post processing to break the set keyframe.
 01969                if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase))
 1970                {
 01971                    args += " -sc_threshold:v:0 0";
 1972                }
 1973            }
 1974            else
 1975            {
 01976                args += keyFrameArg + gopArg;
 1977            }
 1978
 1979            // global_header produced by AMD HEVC VA-API encoder causes non-playable fMP4 on iOS
 01980            if (string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01981                && _mediaEncoder.IsVaapiDeviceAmd)
 1982            {
 01983                args += " -flags:v -global_header";
 1984            }
 1985
 01986            return args;
 1987        }
 1988
 1989        /// <summary>
 1990        /// Gets the video bitrate to specify on the command line.
 1991        /// </summary>
 1992        /// <param name="state">Encoding state.</param>
 1993        /// <param name="videoEncoder">Video encoder to use.</param>
 1994        /// <param name="encodingOptions">Encoding options.</param>
 1995        /// <param name="defaultPreset">Default present to use for encoding.</param>
 1996        /// <returns>Video bitrate.</returns>
 1997        public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, 
 1998        {
 01999            var param = string.Empty;
 2000
 2001            // Tutorials: Enable Intel GuC / HuC firmware loading for Low Power Encoding.
 2002            // https://01.org/group/43/downloads/firmware
 2003            // https://wiki.archlinux.org/title/intel_graphics#Enable_GuC_/_HuC_firmware_loading
 2004            // Intel Low Power Encoding can save unnecessary CPU-GPU synchronization,
 2005            // which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
 02006            var intelLowPowerHwEncoding = false;
 2007
 2008            // Workaround for linux 5.18 to 6.1.3 i915 hang at cost of performance.
 2009            // https://github.com/intel/media-driver/issues/1456
 02010            var enableWaFori915Hang = false;
 2011
 02012            var hardwareAccelerationType = encodingOptions.HardwareAccelerationType;
 2013
 02014            if (hardwareAccelerationType == HardwareAccelerationType.vaapi)
 2015            {
 02016                var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
 2017
 02018                if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
 2019                {
 02020                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder && isIntelVaapiDriver;
 2021                }
 02022                else if (string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
 2023                {
 02024                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder && isIntelVaapiDriver;
 2025                }
 2026            }
 02027            else if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 2028            {
 02029                if (OperatingSystem.IsLinux())
 2030                {
 02031                    var ver = Environment.OSVersion.Version;
 02032                    var isFixedKernel60 = ver.Major == 6 && ver.Minor == 0 && ver >= _minFixedKernel60i915Hang;
 02033                    var isUnaffectedKernel = ver < _minKerneli915Hang || ver > _maxKerneli915Hang;
 2034
 02035                    if (!(isUnaffectedKernel || isFixedKernel60))
 2036                    {
 02037                        var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
 02038                        var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
 02039                                             || vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 02040                        var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
 02041                            && IsVaapiSupported(state)
 02042                            && IsOpenclFullSupported()
 02043                            && !IsIntelVppTonemapAvailable(state, encodingOptions)
 02044                            && IsHwTonemapAvailable(state, encodingOptions);
 2045
 02046                        enableWaFori915Hang = isIntelDecoder && doOclTonemap;
 2047                    }
 2048                }
 2049
 02050                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 2051                {
 02052                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder;
 2053                }
 02054                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2055                {
 02056                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder;
 2057                }
 2058                else
 2059                {
 02060                    enableWaFori915Hang = false;
 2061                }
 2062            }
 2063
 02064            if (intelLowPowerHwEncoding)
 2065            {
 02066                param += " -low_power 1";
 2067            }
 2068
 02069            if (enableWaFori915Hang)
 2070            {
 02071                param += " -async_depth 1";
 2072            }
 2073
 02074            var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
 02075            var encodingPreset = encodingOptions.EncoderPreset;
 2076
 02077            param += GetEncoderParam(encodingPreset, defaultPreset, encodingOptions, videoEncoder, isLibX265);
 02078            param += GetVideoBitrateParam(state, videoEncoder);
 2079
 02080            var framerate = GetFramerateParam(state);
 02081            if (framerate.HasValue)
 2082            {
 02083                param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(CultureInfo.Inv
 2084            }
 2085
 02086            var targetVideoCodec = state.ActualOutputVideoCodec;
 02087            if (string.Equals(targetVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
 02088                || string.Equals(targetVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
 2089            {
 02090                targetVideoCodec = "hevc";
 2091            }
 2092
 02093            var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
 02094            profile = WhiteSpaceRegex().Replace(profile, string.Empty).ToLowerInvariant();
 2095
 02096            var videoProfiles = Array.Empty<string>();
 02097            if (string.Equals("h264", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2098            {
 02099                videoProfiles = _videoProfilesH264;
 2100            }
 02101            else if (string.Equals("hevc", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2102            {
 02103                videoProfiles = _videoProfilesH265;
 2104            }
 02105            else if (string.Equals("av1", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2106            {
 02107                videoProfiles = _videoProfilesAv1;
 2108            }
 2109
 02110            if (!videoProfiles.Contains(profile, StringComparison.OrdinalIgnoreCase))
 2111            {
 02112                profile = string.Empty;
 2113            }
 2114
 2115            // We only transcode to HEVC 8-bit for now, force Main Profile.
 02116            if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)
 02117                || profile.Contains("mainstill", StringComparison.OrdinalIgnoreCase))
 2118            {
 02119                profile = "main";
 2120            }
 2121
 2122            // Extended Profile is not supported by any known h264 encoders, force Main Profile.
 02123            if (profile.Contains("extended", StringComparison.OrdinalIgnoreCase))
 2124            {
 02125                profile = "main";
 2126            }
 2127
 2128            // Only libx264 support encoding H264 High 10 Profile, otherwise force High Profile.
 02129            if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02130                && profile.Contains("high10", StringComparison.OrdinalIgnoreCase))
 2131            {
 02132                profile = "high";
 2133            }
 2134
 2135            // We only need Main profile of AV1 encoders.
 02136            if (videoEncoder.Contains("av1", StringComparison.OrdinalIgnoreCase)
 02137                && (profile.Contains("high", StringComparison.OrdinalIgnoreCase)
 02138                    || profile.Contains("professional", StringComparison.OrdinalIgnoreCase)))
 2139            {
 02140                profile = "main";
 2141            }
 2142
 2143            // h264_vaapi does not support Baseline profile, force Constrained Baseline in this case,
 2144            // which is compatible (and ugly).
 02145            if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02146                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2147            {
 02148                profile = "constrained_baseline";
 2149            }
 2150
 2151            // libx264, h264_{qsv,nvenc,rkmpp} does not support Constrained Baseline profile, force Baseline in this cas
 02152            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02153                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02154                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02155                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02156                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2157            {
 02158                profile = "baseline";
 2159            }
 2160
 2161            // libx264, h264_{qsv,nvenc,vaapi,rkmpp} does not support Constrained High profile, force High in this case.
 02162            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02163                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02164                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02165                 || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02166                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02167                && profile.Contains("high", StringComparison.OrdinalIgnoreCase))
 2168            {
 02169                profile = "high";
 2170            }
 2171
 02172            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02173                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2174            {
 02175                profile = "constrained_baseline";
 2176            }
 2177
 02178            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02179                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2180            {
 02181                profile = "constrained_high";
 2182            }
 2183
 02184            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02185                && profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase))
 2186            {
 02187                profile = "constrained_baseline";
 2188            }
 2189
 02190            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02191                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2192            {
 02193                profile = "constrained_high";
 2194            }
 2195
 02196            if (!string.IsNullOrEmpty(profile))
 2197            {
 2198                // Currently there's no profile option in av1_nvenc encoder
 02199                if (!(string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 02200                      || string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)))
 2201                {
 02202                    param += " -profile:v:0 " + profile;
 2203                }
 2204            }
 2205
 02206            var level = state.GetRequestedLevel(targetVideoCodec);
 2207
 02208            if (!string.IsNullOrEmpty(level))
 2209            {
 02210                level = NormalizeTranscodingLevel(state, level);
 2211
 2212                // libx264, QSV, AMF can adjust the given level to match the output.
 02213                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02214                    || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2215                {
 02216                    param += " -level " + level;
 2217                }
 02218                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2219                {
 2220                    // hevc_qsv use -level 51 instead of -level 153.
 02221                    if (double.TryParse(level, CultureInfo.InvariantCulture, out double hevcLevel))
 2222                    {
 02223                        param += " -level " + (hevcLevel / 3);
 2224                    }
 2225                }
 02226                else if (string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 02227                         || string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 2228                {
 2229                    // libsvtav1 and av1_qsv use -level 60 instead of -level 16
 2230                    // https://aomedia.org/av1/specification/annex-a/
 02231                    if (int.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out int av1Level))
 2232                    {
 02233                        var x = 2 + (av1Level >> 2);
 02234                        var y = av1Level & 3;
 02235                        var res = (x * 10) + y;
 02236                        param += " -level " + res;
 2237                    }
 2238                }
 02239                else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02240                         || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 02241                         || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 2242                {
 02243                    param += " -level " + level;
 2244                }
 02245                else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02246                         || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 02247                         || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase))
 2248                {
 2249                    // level option may cause NVENC to fail.
 2250                    // NVENC cannot adjust the given level, just throw an error.
 2251                }
 02252                else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02253                         || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02254                         || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 2255                {
 2256                    // level option may cause corrupted frames on AMD VAAPI.
 02257                    if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 2258                    {
 02259                        param += " -level " + level;
 2260                    }
 2261                }
 02262                else if (string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 02263                         || string.Equals(videoEncoder, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase))
 2264                {
 02265                    param += " -level " + level;
 2266                }
 02267                else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2268                {
 02269                    param += " -level " + level;
 2270                }
 2271            }
 2272
 02273            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2274            {
 02275                param += " -x264opts:0 subme=0:me_range=16:rc_lookahead=10:me=hex:open_gop=0";
 2276            }
 2277
 02278            if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2279            {
 2280                // libx265 only accept level option in -x265-params.
 2281                // level option may cause libx265 to fail.
 2282                // libx265 cannot adjust the given level, just throw an error.
 02283                param += " -x265-params:0 no-scenecut=1:no-open-gop=1:no-info=1";
 2284
 02285                if (encodingOptions.EncoderPreset < EncoderPreset.ultrafast)
 2286                {
 2287                    // The following params are slower than the ultrafast preset, don't use when ultrafast is selected.
 02288                    param += ":subme=3:merange=25:rc-lookahead=10:me=star:ctu=32:max-tu-size=32:min-cu-size=16:rskip=2:r
 2289                }
 2290            }
 2291
 02292            if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)
 02293                && _mediaEncoder.EncoderVersion >= _minFFmpegSvtAv1Params)
 2294            {
 02295                param += " -svtav1-params:0 rc=1:tune=0:film-grain=0:enable-overlays=1:enable-tf=0";
 2296            }
 2297
 2298            /* Access unit too large: 8192 < 20880 error */
 02299            if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) ||
 02300                 string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) &&
 02301                 _mediaEncoder.EncoderVersion >= _minFFmpegVaapiH26xEncA53CcSei)
 2302            {
 02303                param += " -sei -a53_cc";
 2304            }
 2305
 02306            return param;
 2307        }
 2308
 2309        public bool CanStreamCopyVideo(EncodingJobInfo state, MediaStream videoStream)
 2310        {
 02311            var request = state.BaseRequest;
 2312
 02313            if (!request.AllowVideoStreamCopy)
 2314            {
 02315                return false;
 2316            }
 2317
 02318            if (videoStream.IsInterlaced
 02319                && state.DeInterlace(videoStream.Codec, false))
 2320            {
 02321                return false;
 2322            }
 2323
 02324            if (videoStream.IsAnamorphic ?? false)
 2325            {
 02326                if (request.RequireNonAnamorphic)
 2327                {
 02328                    return false;
 2329                }
 2330            }
 2331
 2332            // Can't stream copy if we're burning in subtitles
 02333            if (request.SubtitleStreamIndex.HasValue
 02334                && request.SubtitleStreamIndex.Value >= 0
 02335                && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
 2336            {
 02337                return false;
 2338            }
 2339
 02340            if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 02341                && videoStream.IsAVC.HasValue
 02342                && !videoStream.IsAVC.Value
 02343                && request.RequireAvc)
 2344            {
 02345                return false;
 2346            }
 2347
 2348            // Source and target codecs must match
 02349            if (string.IsNullOrEmpty(videoStream.Codec)
 02350                || (state.SupportedVideoCodecs.Length != 0
 02351                    && !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparison.OrdinalIgnoreCase)))
 2352            {
 02353                return false;
 2354            }
 2355
 02356            var requestedProfiles = state.GetRequestedProfiles(videoStream.Codec);
 2357
 2358            // If client is requesting a specific video profile, it must match the source
 02359            if (requestedProfiles.Length > 0)
 2360            {
 02361                if (string.IsNullOrEmpty(videoStream.Profile))
 2362                {
 2363                    // return false;
 2364                }
 2365
 02366                var requestedProfile = requestedProfiles[0];
 2367                // strip spaces because they may be stripped out on the query string as well
 02368                if (!string.IsNullOrEmpty(videoStream.Profile)
 02369                    && !requestedProfiles.Contains(videoStream.Profile.Replace(" ", string.Empty, StringComparison.Ordin
 2370                {
 02371                    var currentScore = GetVideoProfileScore(videoStream.Codec, videoStream.Profile);
 02372                    var requestedScore = GetVideoProfileScore(videoStream.Codec, requestedProfile);
 2373
 02374                    if (currentScore == -1 || currentScore > requestedScore)
 2375                    {
 02376                        return false;
 2377                    }
 2378                }
 2379            }
 2380
 02381            var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
 02382            if (requestedRangeTypes.Length > 0)
 2383            {
 02384                if (videoStream.VideoRangeType == VideoRangeType.Unknown)
 2385                {
 02386                    return false;
 2387                }
 2388
 2389                // DOVIWithHDR10 should be compatible with HDR10 supporting players. Same goes with HLG and of course SD
 02390                var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ord
 02391                var requestHasHLG = requestedRangeTypes.Contains(VideoRangeType.HLG.ToString(), StringComparison.Ordinal
 02392                var requestHasSDR = requestedRangeTypes.Contains(VideoRangeType.SDR.ToString(), StringComparison.Ordinal
 02393                var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.Ordin
 2394
 2395                // If SDR is the only supported range, we should not copy any of the HDR streams.
 2396                // All the following copy check assumes at least one HDR format is supported.
 02397                if (requestedRangeTypes.Length == 1 && requestHasSDR && videoStream.VideoRangeType != VideoRangeType.SDR
 2398                {
 02399                    return false;
 2400                }
 2401
 2402                // If the client does not support DOVI and the video stream is DOVI without fallback, we should not copy
 02403                if (!requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVI)
 2404                {
 02405                    return false;
 2406                }
 2407
 02408                if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreC
 02409                     && !((requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10)
 02410                            || (requestHasHLG && videoStream.VideoRangeType == VideoRangeType.DOVIWithHLG)
 02411                            || (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)
 02412                            || (requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.HDR10Plus)))
 2413                {
 2414                    // If the video stream is in HDR10+ or a static HDR format, don't allow copy if the client does not 
 02415                    if (videoStream.VideoRangeType is VideoRangeType.HDR10Plus or VideoRangeType.HDR10 or VideoRangeType
 2416                    {
 02417                        return false;
 2418                    }
 2419
 2420                    // Check complicated cases where we need to remove dynamic metadata
 2421                    // Conservatively refuse to copy if the encoder can't remove dynamic metadata,
 2422                    // but a removal is required for compatability reasons.
 02423                    var dynamicHdrMetadataRemovalPlan = ShouldRemoveDynamicHdrMetadata(state);
 02424                    if (!CanEncoderRemoveDynamicHdrMetadata(dynamicHdrMetadataRemovalPlan, videoStream))
 2425                    {
 02426                        return false;
 2427                    }
 2428                }
 2429            }
 2430
 2431            // Video width must fall within requested value
 02432            if (request.MaxWidth.HasValue
 02433                && (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
 2434            {
 02435                return false;
 2436            }
 2437
 2438            // Video height must fall within requested value
 02439            if (request.MaxHeight.HasValue
 02440                && (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value))
 2441            {
 02442                return false;
 2443            }
 2444
 2445            // Video framerate must fall within requested value
 02446            var requestedFramerate = request.MaxFramerate ?? request.Framerate;
 02447            if (requestedFramerate.HasValue)
 2448            {
 02449                var videoFrameRate = videoStream.ReferenceFrameRate;
 2450
 2451                // Add a little tolerance to the framerate check because some videos might record a framerate
 2452                // that is slightly greater than the intended framerate, but the device can still play it correctly.
 2453                // 0.05 fps tolerance should be safe enough.
 02454                if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value + 0.05f)
 2455                {
 02456                    return false;
 2457                }
 2458            }
 2459
 2460            // Video bitrate must fall within requested value
 02461            if (request.VideoBitRate.HasValue
 02462                && (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value))
 2463            {
 2464                // For LiveTV that has no bitrate, let's try copy if other conditions are met
 02465                if (string.IsNullOrWhiteSpace(request.LiveStreamId) || videoStream.BitRate.HasValue)
 2466                {
 02467                    return false;
 2468                }
 2469            }
 2470
 02471            var maxBitDepth = state.GetRequestedVideoBitDepth(videoStream.Codec);
 02472            if (maxBitDepth.HasValue)
 2473            {
 02474                if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > maxBitDepth.Value)
 2475                {
 02476                    return false;
 2477                }
 2478            }
 2479
 02480            var maxRefFrames = state.GetRequestedMaxRefFrames(videoStream.Codec);
 02481            if (maxRefFrames.HasValue
 02482                && videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > maxRefFrames.Value)
 2483            {
 02484                return false;
 2485            }
 2486
 2487            // If a specific level was requested, the source must match or be less than
 02488            var level = state.GetRequestedLevel(videoStream.Codec);
 02489            if (double.TryParse(level, CultureInfo.InvariantCulture, out var requestLevel))
 2490            {
 02491                if (!videoStream.Level.HasValue)
 2492                {
 2493                    // return false;
 2494                }
 2495
 02496                if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
 2497                {
 02498                    return false;
 2499                }
 2500            }
 2501
 02502            if (string.Equals(state.InputContainer, "avi", StringComparison.OrdinalIgnoreCase)
 02503                && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)
 02504                && !(videoStream.IsAVC ?? false))
 2505            {
 2506                // see Coach S01E01 - Kelly and the Professor(0).avi
 02507                return false;
 2508            }
 2509
 02510            return true;
 2511        }
 2512
 2513        public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, IEnumerable<string> supportedAudi
 2514        {
 02515            var request = state.BaseRequest;
 2516
 02517            if (!request.AllowAudioStreamCopy)
 2518            {
 02519                return false;
 2520            }
 2521
 02522            var maxBitDepth = state.GetRequestedAudioBitDepth(audioStream.Codec);
 02523            if (maxBitDepth.HasValue
 02524                && audioStream.BitDepth.HasValue
 02525                && audioStream.BitDepth.Value > maxBitDepth.Value)
 2526            {
 02527                return false;
 2528            }
 2529
 2530            // Source and target codecs must match
 02531            if (string.IsNullOrEmpty(audioStream.Codec)
 02532                || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparison.OrdinalIgnoreCase))
 2533            {
 02534                return false;
 2535            }
 2536
 2537            // Channels must fall within requested value
 02538            var channels = state.GetRequestedAudioChannels(audioStream.Codec);
 02539            if (channels.HasValue)
 2540            {
 02541                if (!audioStream.Channels.HasValue || audioStream.Channels.Value <= 0)
 2542                {
 02543                    return false;
 2544                }
 2545
 02546                if (audioStream.Channels.Value > channels.Value)
 2547                {
 02548                    return false;
 2549                }
 2550            }
 2551
 2552            // Sample rate must fall within requested value
 02553            if (request.AudioSampleRate.HasValue)
 2554            {
 02555                if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value <= 0)
 2556                {
 02557                    return false;
 2558                }
 2559
 02560                if (audioStream.SampleRate.Value > request.AudioSampleRate.Value)
 2561                {
 02562                    return false;
 2563                }
 2564            }
 2565
 2566            // Audio bitrate must fall within requested value
 02567            if (request.AudioBitRate.HasValue
 02568                && audioStream.BitRate.HasValue
 02569                && audioStream.BitRate.Value > request.AudioBitRate.Value)
 2570            {
 02571                return false;
 2572            }
 2573
 02574            return request.EnableAutoStreamCopy;
 2575        }
 2576
 2577        public int GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideo
 2578        {
 02579            var bitrate = request.VideoBitRate;
 2580
 02581            if (videoStream is not null)
 2582            {
 02583                var isUpscaling = request.Height.HasValue
 02584                    && videoStream.Height.HasValue
 02585                    && request.Height.Value > videoStream.Height.Value
 02586                    && request.Width.HasValue
 02587                    && videoStream.Width.HasValue
 02588                    && request.Width.Value > videoStream.Width.Value;
 2589
 2590                // Don't allow bitrate increases unless upscaling
 02591                if (!isUpscaling && bitrate.HasValue && videoStream.BitRate.HasValue)
 2592                {
 02593                    bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value);
 2594                }
 2595
 02596                if (bitrate.HasValue)
 2597                {
 02598                    var inputVideoCodec = videoStream.Codec;
 02599                    bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
 2600
 2601                    // If a max bitrate was requested, don't let the scaled bitrate exceed it
 02602                    if (request.VideoBitRate.HasValue)
 2603                    {
 02604                        bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
 2605                    }
 2606                }
 2607            }
 2608
 2609            // Cap the max target bitrate to intMax/2 to satisfy the bufsize=bitrate*2.
 02610            return Math.Min(bitrate ?? 0, int.MaxValue / 2);
 2611        }
 2612
 2613        private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
 2614        {
 2615            // these values were chosen from testing to improve low bitrate streams
 02616            if (sourceBitrate <= 2000000)
 2617            {
 02618                sourceBitrate = Convert.ToInt32(sourceBitrate * 2.5);
 2619            }
 02620            else if (sourceBitrate <= 3000000)
 2621            {
 02622                sourceBitrate *= 2;
 2623            }
 2624
 02625            var bitrate = Math.Min(sourceBitrate, requestedBitrate);
 2626
 02627            return bitrate;
 2628        }
 2629
 2630        private static double GetVideoBitrateScaleFactor(string codec)
 2631        {
 2632            // hevc & vp9 - 40% more efficient than h.264
 02633            if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 02634                || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
 02635                || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
 2636            {
 02637                return .6;
 2638            }
 2639
 2640            // av1 - 50% more efficient than h.264
 02641            if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 2642            {
 02643                return .5;
 2644            }
 2645
 02646            return 1;
 2647        }
 2648
 2649        public static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
 2650        {
 02651            var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
 02652            var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
 2653
 2654            // Don't scale the real bitrate lower than the requested bitrate
 02655            var scaleFactor = Math.Max(outputScaleFactor / inputScaleFactor, 1);
 2656
 02657            if (bitrate <= 500000)
 2658            {
 02659                scaleFactor = Math.Max(scaleFactor, 4);
 2660            }
 02661            else if (bitrate <= 1000000)
 2662            {
 02663                scaleFactor = Math.Max(scaleFactor, 3);
 2664            }
 02665            else if (bitrate <= 2000000)
 2666            {
 02667                scaleFactor = Math.Max(scaleFactor, 2.5);
 2668            }
 02669            else if (bitrate <= 3000000)
 2670            {
 02671                scaleFactor = Math.Max(scaleFactor, 2);
 2672            }
 02673            else if (bitrate >= 30000000)
 2674            {
 2675                // Don't scale beyond 30Mbps, it is hardly visually noticeable for most codecs with our prefer speed enc
 2676                // and will cause extremely high bitrate to be used for av1->h264 transcoding that will overload clients
 02677                scaleFactor = 1;
 2678            }
 2679
 02680            return Convert.ToInt32(scaleFactor * bitrate);
 2681        }
 2682
 2683        public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChanne
 2684        {
 02685            return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels);
 2686        }
 2687
 2688        public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudio
 2689        {
 02690            if (audioStream is null)
 2691            {
 02692                return null;
 2693            }
 2694
 02695            var inputChannels = audioStream.Channels ?? 0;
 02696            var outputChannels = outputAudioChannels ?? 0;
 02697            var bitrate = audioBitRate ?? int.MaxValue;
 2698
 02699            if (string.IsNullOrEmpty(audioCodec)
 02700                || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
 02701                || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
 02702                || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
 02703                || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
 02704                || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
 02705                || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
 2706            {
 02707                return (inputChannels, outputChannels) switch
 02708                {
 02709                    (>= 6, >= 6 or 0) => Math.Min(640000, bitrate),
 02710                    (> 0, > 0) => Math.Min(outputChannels * 128000, bitrate),
 02711                    (> 0, _) => Math.Min(inputChannels * 128000, bitrate),
 02712                    (_, _) => Math.Min(384000, bitrate)
 02713                };
 2714            }
 2715
 02716            if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
 02717                || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
 2718            {
 02719                return (inputChannels, outputChannels) switch
 02720                {
 02721                    (>= 6, >= 6 or 0) => Math.Min(768000, bitrate),
 02722                    (> 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
 02723                    (> 0, _) => Math.Min(inputChannels * 136000, bitrate),
 02724                    (_, _) => Math.Min(672000, bitrate)
 02725                };
 2726            }
 2727
 2728            // Empty bitrate area is not allow on iOS
 2729            // Default audio bitrate to 128K per channel if we don't have codec specific defaults
 2730            // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
 02731            return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
 2732        }
 2733
 2734        public string GetAudioVbrModeParam(string encoder, int bitrate, int channels)
 2735        {
 02736            var bitratePerChannel = bitrate / Math.Max(channels, 1);
 02737            if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
 2738            {
 02739                return " -vbr:a " + bitratePerChannel switch
 02740                {
 02741                    < 32000 => "1",
 02742                    < 48000 => "2",
 02743                    < 64000 => "3",
 02744                    < 96000 => "4",
 02745                    _ => "5"
 02746                };
 2747            }
 2748
 02749            if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase))
 2750            {
 2751                // lame's VBR is only good for a certain bitrate range
 2752                // For very low and very high bitrate, use abr mode
 02753                if (bitratePerChannel is < 122500 and > 48000)
 2754                {
 02755                    return " -qscale:a " + bitratePerChannel switch
 02756                    {
 02757                        < 64000 => "6",
 02758                        < 88000 => "4",
 02759                        < 112000 => "2",
 02760                        _ => "0"
 02761                    };
 2762                }
 2763
 02764                return " -abr:a 1" + " -b:a " + bitrate;
 2765            }
 2766
 02767            if (string.Equals(encoder, "aac_at", StringComparison.OrdinalIgnoreCase))
 2768            {
 2769                // aac_at's CVBR mode
 02770                return " -aac_at_mode:a 2" + " -b:a " + bitrate;
 2771            }
 2772
 02773            if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase))
 2774            {
 02775                return " -qscale:a " + bitratePerChannel switch
 02776                {
 02777                    < 40000 => "0",
 02778                    < 56000 => "2",
 02779                    < 80000 => "4",
 02780                    < 112000 => "6",
 02781                    _ => "8"
 02782                };
 2783            }
 2784
 02785            return null;
 2786        }
 2787
 2788        public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
 2789        {
 02790            var channels = state.OutputAudioChannels;
 2791
 02792            var filters = new List<string>();
 2793
 02794            if (channels is 2 && state.AudioStream?.Channels is > 2)
 2795            {
 02796                var hasDownMixFilter = DownMixAlgorithmsHelper.AlgorithmFilterStrings.TryGetValue((encodingOptions.DownM
 02797                if (hasDownMixFilter)
 2798                {
 02799                    filters.Add(downMixFilterString);
 2800                }
 2801
 02802                if (!encodingOptions.DownMixAudioBoost.Equals(1))
 2803                {
 02804                    filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
 2805                }
 2806            }
 2807
 02808            var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
 02809            if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && ShouldEncodeSubtitle(st
 2810            {
 02811                var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds;
 2812
 02813                filters.Add(
 02814                    string.Format(
 02815                        CultureInfo.InvariantCulture,
 02816                        "asetpts=PTS-{0}/TB",
 02817                        Math.Round(seconds)));
 2818            }
 2819
 02820            if (filters.Count > 0)
 2821            {
 02822                return " -af \"" + string.Join(',', filters) + "\"";
 2823            }
 2824
 02825            return string.Empty;
 2826        }
 2827
 2828        /// <summary>
 2829        /// Gets the number of audio channels to specify on the command line.
 2830        /// </summary>
 2831        /// <param name="state">The state.</param>
 2832        /// <param name="audioStream">The audio stream.</param>
 2833        /// <param name="outputAudioCodec">The output audio codec.</param>
 2834        /// <returns>System.Nullable{System.Int32}.</returns>
 2835        public int? GetNumAudioChannelsParam(EncodingJobInfo state, MediaStream audioStream, string outputAudioCodec)
 2836        {
 02837            if (audioStream is null)
 2838            {
 02839                return null;
 2840            }
 2841
 02842            var request = state.BaseRequest;
 2843
 02844            var codec = outputAudioCodec ?? string.Empty;
 2845
 02846            int? resultChannels = state.GetRequestedAudioChannels(codec);
 2847
 02848            var inputChannels = audioStream.Channels;
 2849
 02850            if (inputChannels > 0)
 2851            {
 02852                resultChannels = inputChannels < resultChannels ? inputChannels : resultChannels ?? inputChannels;
 2853            }
 2854
 02855            var isTranscodingAudio = !IsCopyCodec(codec);
 2856
 02857            if (isTranscodingAudio)
 2858            {
 02859                var audioEncoder = GetAudioEncoder(state);
 02860                if (!_audioTranscodeChannelLookup.TryGetValue(audioEncoder, out var transcoderChannelLimit))
 2861                {
 2862                    // Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many c
 02863                    transcoderChannelLimit = 8;
 2864                }
 2865
 2866                // Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelL
 02867                resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? tr
 2868
 02869                if (request.TranscodingMaxAudioChannels < resultChannels)
 2870                {
 02871                    resultChannels = request.TranscodingMaxAudioChannels;
 2872                }
 2873
 2874                // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
 2875                // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_d
 02876                if (state.TranscodingType != TranscodingJobType.Progressive
 02877                    && ((resultChannels > 2 && resultChannels < 6) || resultChannels == 7))
 2878                {
 2879                    // We can let FFMpeg supply an extra LFE channel for 5ch and 7ch to make them 5.1 and 7.1
 02880                    if (resultChannels == 5)
 2881                    {
 02882                        resultChannels = 6;
 2883                    }
 02884                    else if (resultChannels == 7)
 2885                    {
 02886                        resultChannels = 8;
 2887                    }
 2888                    else
 2889                    {
 2890                        // For other weird layout, just downmix to stereo for compatibility
 02891                        resultChannels = 2;
 2892                    }
 2893                }
 2894            }
 2895
 02896            return resultChannels;
 2897        }
 2898
 2899        /// <summary>
 2900        /// Enforces the resolution limit.
 2901        /// </summary>
 2902        /// <param name="state">The state.</param>
 2903        public void EnforceResolutionLimit(EncodingJobInfo state)
 2904        {
 02905            var videoRequest = state.BaseRequest;
 2906
 2907            // Switch the incoming params to be ceilings rather than fixed values
 02908            videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
 02909            videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
 2910
 02911            videoRequest.Width = null;
 02912            videoRequest.Height = null;
 02913        }
 2914
 2915        /// <summary>
 2916        /// Gets the fast seek command line parameter.
 2917        /// </summary>
 2918        /// <param name="state">The state.</param>
 2919        /// <param name="options">The options.</param>
 2920        /// <param name="segmentContainer">Segment Container.</param>
 2921        /// <returns>System.String.</returns>
 2922        /// <value>The fast seek command line parameter.</value>
 2923        public string GetFastSeekCommandLineParameter(EncodingJobInfo state, EncodingOptions options, string segmentCont
 2924        {
 02925            var time = state.BaseRequest.StartTimeTicks ?? 0;
 02926            var maxTime = state.RunTimeTicks ?? 0;
 02927            var seekParam = string.Empty;
 2928
 02929            if (time > 0)
 2930            {
 2931                // For direct streaming/remuxing, HLS segments start at keyframes.
 2932                // However, ffmpeg will seek to previous keyframe when the exact frame time is the input
 2933                // Workaround this by adding 0.5s offset to the seeking time to get the exact keyframe on most videos.
 2934                // This will help subtitle syncing.
 02935                var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCod
 02936                var seekTick = isHlsRemuxing ? time + 5000000L : time;
 2937
 2938                // Seeking beyond EOF makes no sense in transcoding. Clamp the seekTick value to
 2939                // [0, RuntimeTicks - 5.0s], so that the muxer gets packets and avoid error codes.
 02940                if (maxTime > 0)
 2941                {
 02942                    seekTick = Math.Clamp(seekTick, 0, Math.Max(maxTime - 50000000L, 0));
 2943                }
 2944
 02945                seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(seekT
 2946
 02947                if (state.IsVideoRequest)
 2948                {
 2949                    // If we are remuxing, then the copied stream cannot be seeked accurately (it will seek to the neare
 2950                    // keyframe). If we are using fMP4, then force all other streams to use the same inaccurate seeking 
 2951                    // avoid A/V sync issues which cause playback issues on some devices.
 2952                    // When remuxing video, the segment start times correspond to key frames in the source stream, so th
 2953                    // option shouldn't change the seeked point that much.
 2954                    // Important: make sure not to use it with wtv because it breaks seeking
 02955                    if (state.TranscodingType is TranscodingJobType.Hls
 02956                        && string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase)
 02957                        && (IsCopyCodec(state.OutputVideoCodec) || IsCopyCodec(state.OutputAudioCodec))
 02958                        && !string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase))
 2959                    {
 02960                        seekParam += " -noaccurate_seek";
 2961                    }
 2962                }
 2963            }
 2964
 02965            return seekParam;
 2966        }
 2967
 2968        /// <summary>
 2969        /// Gets the map args.
 2970        /// </summary>
 2971        /// <param name="state">The state.</param>
 2972        /// <returns>System.String.</returns>
 2973        public string GetMapArgs(EncodingJobInfo state)
 2974        {
 2975            // If we don't have known media info
 2976            // If input is video, use -sn to drop subtitles
 2977            // Otherwise just return empty
 02978            if (state.VideoStream is null && state.AudioStream is null)
 2979            {
 02980                return state.IsInputVideo ? "-sn" : string.Empty;
 2981            }
 2982
 2983            // We have media info, but we don't know the stream index
 02984            if (state.VideoStream is not null && state.VideoStream.Index == -1)
 2985            {
 02986                return "-sn";
 2987            }
 2988
 2989            // We have media info, but we don't know the stream index
 02990            if (state.AudioStream is not null && state.AudioStream.Index == -1)
 2991            {
 02992                return state.IsInputVideo ? "-sn" : string.Empty;
 2993            }
 2994
 02995            var args = string.Empty;
 2996
 02997            if (state.VideoStream is not null)
 2998            {
 02999                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 3000
 03001                args += string.Format(
 03002                    CultureInfo.InvariantCulture,
 03003                    "-map 0:{0}",
 03004                    videoStreamIndex);
 3005            }
 3006            else
 3007            {
 3008                // No known video stream
 03009                args += "-vn";
 3010            }
 3011
 03012            if (state.AudioStream is not null)
 3013            {
 03014                int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
 03015                if (state.AudioStream.IsExternal)
 3016                {
 03017                    bool hasExternalGraphicsSubs = state.SubtitleStream is not null
 03018                        && ShouldEncodeSubtitle(state)
 03019                        && state.SubtitleStream.IsExternal
 03020                        && !state.SubtitleStream.IsTextSubtitleStream;
 03021                    int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
 3022
 03023                    args += string.Format(
 03024                        CultureInfo.InvariantCulture,
 03025                        " -map {0}:{1}",
 03026                        externalAudioMapIndex,
 03027                        audioStreamIndex);
 3028                }
 3029                else
 3030                {
 03031                    args += string.Format(
 03032                        CultureInfo.InvariantCulture,
 03033                        " -map 0:{0}",
 03034                        audioStreamIndex);
 3035                }
 3036            }
 3037            else
 3038            {
 03039                args += " -map -0:a";
 3040            }
 3041
 03042            var subtitleMethod = state.SubtitleDeliveryMethod;
 03043            if (state.SubtitleStream is null || subtitleMethod == SubtitleDeliveryMethod.Hls)
 3044            {
 03045                args += " -map -0:s";
 3046            }
 03047            else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
 3048            {
 03049                int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3050
 03051                args += string.Format(
 03052                    CultureInfo.InvariantCulture,
 03053                    " -map 0:{0}",
 03054                    subtitleStreamIndex);
 3055            }
 03056            else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
 3057            {
 03058                int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3059
 03060                args += string.Format(
 03061                    CultureInfo.InvariantCulture,
 03062                    " -map 1:{0} -sn",
 03063                    externalSubtitleStreamIndex);
 3064            }
 3065
 03066            return args;
 3067        }
 3068
 3069        /// <summary>
 3070        /// Gets the negative map args by filters.
 3071        /// </summary>
 3072        /// <param name="state">The state.</param>
 3073        /// <param name="videoProcessFilters">The videoProcessFilters.</param>
 3074        /// <returns>System.String.</returns>
 3075        public string GetNegativeMapArgsByFilters(EncodingJobInfo state, string videoProcessFilters)
 3076        {
 03077            string args = string.Empty;
 3078
 3079            // http://ffmpeg.org/ffmpeg-all.html#toc-Complex-filtergraphs-1
 03080            if (state.VideoStream is not null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordina
 3081            {
 03082                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 3083
 03084                args += string.Format(
 03085                    CultureInfo.InvariantCulture,
 03086                    "-map -0:{0} ",
 03087                    videoStreamIndex);
 3088            }
 3089
 03090            return args;
 3091        }
 3092
 3093        /// <summary>
 3094        /// Determines which stream will be used for playback.
 3095        /// </summary>
 3096        /// <param name="allStream">All stream.</param>
 3097        /// <param name="desiredIndex">Index of the desired.</param>
 3098        /// <param name="type">The type.</param>
 3099        /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
 3100        /// <returns>MediaStream.</returns>
 3101        public MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, b
 3102        {
 03103            var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
 3104
 03105            if (desiredIndex.HasValue)
 3106            {
 03107                var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
 3108
 03109                if (stream is not null)
 3110                {
 03111                    return stream;
 3112                }
 3113            }
 3114
 03115            if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
 3116            {
 03117                return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
 03118                       streams.FirstOrDefault();
 3119            }
 3120
 3121            // Just return the first one
 03122            return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
 3123        }
 3124
 3125        public static (int? Width, int? Height) GetFixedOutputSize(
 3126            int? videoWidth,
 3127            int? videoHeight,
 3128            int? requestedWidth,
 3129            int? requestedHeight,
 3130            int? requestedMaxWidth,
 3131            int? requestedMaxHeight)
 3132        {
 03133            if (!videoWidth.HasValue && !requestedWidth.HasValue)
 3134            {
 03135                return (null, null);
 3136            }
 3137
 03138            if (!videoHeight.HasValue && !requestedHeight.HasValue)
 3139            {
 03140                return (null, null);
 3141            }
 3142
 03143            int inputWidth = Convert.ToInt32(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture);
 03144            int inputHeight = Convert.ToInt32(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture);
 03145            int outputWidth = requestedWidth ?? inputWidth;
 03146            int outputHeight = requestedHeight ?? inputHeight;
 3147
 3148            // Don't transcode video to bigger than 4k when using HW.
 03149            int maximumWidth = Math.Min(requestedMaxWidth ?? outputWidth, 4096);
 03150            int maximumHeight = Math.Min(requestedMaxHeight ?? outputHeight, 4096);
 3151
 03152            if (outputWidth > maximumWidth || outputHeight > maximumHeight)
 3153            {
 03154                var scaleW = (double)maximumWidth / outputWidth;
 03155                var scaleH = (double)maximumHeight / outputHeight;
 03156                var scale = Math.Min(scaleW, scaleH);
 03157                outputWidth = Math.Min(maximumWidth, Convert.ToInt32(outputWidth * scale));
 03158                outputHeight = Math.Min(maximumHeight, Convert.ToInt32(outputHeight * scale));
 3159            }
 3160
 03161            outputWidth = 2 * (outputWidth / 2);
 03162            outputHeight = 2 * (outputHeight / 2);
 3163
 03164            return (outputWidth, outputHeight);
 3165        }
 3166
 3167        public static bool IsScaleRatioSupported(
 3168            int? videoWidth,
 3169            int? videoHeight,
 3170            int? requestedWidth,
 3171            int? requestedHeight,
 3172            int? requestedMaxWidth,
 3173            int? requestedMaxHeight,
 3174            double? maxScaleRatio)
 3175        {
 03176            var (outWidth, outHeight) = GetFixedOutputSize(
 03177                videoWidth,
 03178                videoHeight,
 03179                requestedWidth,
 03180                requestedHeight,
 03181                requestedMaxWidth,
 03182                requestedMaxHeight);
 3183
 03184            if (!videoWidth.HasValue
 03185                 || !videoHeight.HasValue
 03186                 || !outWidth.HasValue
 03187                 || !outHeight.HasValue
 03188                 || !maxScaleRatio.HasValue
 03189                 || (maxScaleRatio.Value < 1.0f))
 3190            {
 03191                return false;
 3192            }
 3193
 03194            var minScaleRatio = 1.0f / maxScaleRatio;
 03195            var scaleRatioW = (double)outWidth / (double)videoWidth;
 03196            var scaleRatioH = (double)outHeight / (double)videoHeight;
 3197
 03198            if (scaleRatioW < minScaleRatio
 03199                || scaleRatioW > maxScaleRatio
 03200                || scaleRatioH < minScaleRatio
 03201                || scaleRatioH > maxScaleRatio)
 3202            {
 03203                return false;
 3204            }
 3205
 03206            return true;
 3207        }
 3208
 3209        public static string GetHwScaleFilter(
 3210            string hwScalePrefix,
 3211            string hwScaleSuffix,
 3212            string videoFormat,
 3213            bool swapOutputWandH,
 3214            int? videoWidth,
 3215            int? videoHeight,
 3216            int? requestedWidth,
 3217            int? requestedHeight,
 3218            int? requestedMaxWidth,
 3219            int? requestedMaxHeight)
 3220        {
 03221            var (outWidth, outHeight) = GetFixedOutputSize(
 03222                videoWidth,
 03223                videoHeight,
 03224                requestedWidth,
 03225                requestedHeight,
 03226                requestedMaxWidth,
 03227                requestedMaxHeight);
 3228
 03229            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03230            var isSizeFixed = !videoWidth.HasValue
 03231                || outWidth.Value != videoWidth.Value
 03232                || !videoHeight.HasValue
 03233                || outHeight.Value != videoHeight.Value;
 3234
 03235            var swpOutW = swapOutputWandH ? outHeight.Value : outWidth.Value;
 03236            var swpOutH = swapOutputWandH ? outWidth.Value : outHeight.Value;
 3237
 03238            var arg1 = isSizeFixed ? $"=w={swpOutW}:h={swpOutH}" : string.Empty;
 03239            var arg2 = isFormatFixed ? $"format={videoFormat}" : string.Empty;
 03240            if (isFormatFixed)
 3241            {
 03242                arg2 = (isSizeFixed ? ':' : '=') + arg2;
 3243            }
 3244
 03245            if (!string.IsNullOrEmpty(hwScaleSuffix) && (isSizeFixed || isFormatFixed))
 3246            {
 03247                return string.Format(
 03248                    CultureInfo.InvariantCulture,
 03249                    "{0}_{1}{2}{3}",
 03250                    hwScalePrefix ?? "scale",
 03251                    hwScaleSuffix,
 03252                    arg1,
 03253                    arg2);
 3254            }
 3255
 03256            return string.Empty;
 3257        }
 3258
 3259        public static string GetGraphicalSubPreProcessFilters(
 3260            int? videoWidth,
 3261            int? videoHeight,
 3262            int? subtitleWidth,
 3263            int? subtitleHeight,
 3264            int? requestedWidth,
 3265            int? requestedHeight,
 3266            int? requestedMaxWidth,
 3267            int? requestedMaxHeight)
 3268        {
 03269            var (outWidth, outHeight) = GetFixedOutputSize(
 03270                videoWidth,
 03271                videoHeight,
 03272                requestedWidth,
 03273                requestedHeight,
 03274                requestedMaxWidth,
 03275                requestedMaxHeight);
 3276
 03277            if (!outWidth.HasValue
 03278                || !outHeight.HasValue
 03279                || outWidth.Value <= 0
 03280                || outHeight.Value <= 0)
 3281            {
 03282                return string.Empty;
 3283            }
 3284
 3285            // Automatically add padding based on subtitle input
 03286            var filters = @"scale,scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:blac
 3287
 03288            if (subtitleWidth.HasValue
 03289                && subtitleHeight.HasValue
 03290                && subtitleWidth.Value > 0
 03291                && subtitleHeight.Value > 0)
 3292            {
 03293                var videoDar = (double)outWidth.Value / outHeight.Value;
 03294                var subtitleDar = (double)subtitleWidth.Value / subtitleHeight.Value;
 3295
 3296                // No need to add padding when DAR is the same -> 1080p PGSSUB on 2160p video
 03297                if (Math.Abs(videoDar - subtitleDar) < 0.01f)
 3298                {
 03299                    filters = @"scale,scale={0}:{1}:fast_bilinear";
 3300                }
 3301            }
 3302
 03303            return string.Format(
 03304                CultureInfo.InvariantCulture,
 03305                filters,
 03306                outWidth.Value,
 03307                outHeight.Value);
 3308        }
 3309
 3310        public static string GetAlphaSrcFilter(
 3311            EncodingJobInfo state,
 3312            int? videoWidth,
 3313            int? videoHeight,
 3314            int? requestedWidth,
 3315            int? requestedHeight,
 3316            int? requestedMaxWidth,
 3317            int? requestedMaxHeight,
 3318            float? framerate)
 3319        {
 03320            var reqTicks = state.BaseRequest.StartTimeTicks ?? 0;
 03321            var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture
 03322            var (outWidth, outHeight) = GetFixedOutputSize(
 03323                videoWidth,
 03324                videoHeight,
 03325                requestedWidth,
 03326                requestedHeight,
 03327                requestedMaxWidth,
 03328                requestedMaxHeight);
 3329
 03330            if (outWidth.HasValue && outHeight.HasValue)
 3331            {
 03332                return string.Format(
 03333                    CultureInfo.InvariantCulture,
 03334                    "alphasrc=s={0}x{1}:r={2}:start='{3}'",
 03335                    outWidth.Value,
 03336                    outHeight.Value,
 03337                    framerate ?? 25,
 03338                    reqTicks > 0 ? startTime : 0);
 3339            }
 3340
 03341            return string.Empty;
 3342        }
 3343
 3344        public static string GetSwScaleFilter(
 3345            EncodingJobInfo state,
 3346            EncodingOptions options,
 3347            string videoEncoder,
 3348            int? videoWidth,
 3349            int? videoHeight,
 3350            Video3DFormat? threedFormat,
 3351            int? requestedWidth,
 3352            int? requestedHeight,
 3353            int? requestedMaxWidth,
 3354            int? requestedMaxHeight)
 3355        {
 03356            var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 03357            var isMjpeg = videoEncoder is not null && videoEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase)
 03358            var scaleVal = isV4l2 ? 64 : 2;
 03359            var targetAr = isMjpeg ? "(a*sar)" : "a"; // manually calculate AR when using mjpeg encoder
 3360
 3361            // If fixed dimensions were supplied
 03362            if (requestedWidth.HasValue && requestedHeight.HasValue)
 3363            {
 03364                if (isV4l2)
 3365                {
 03366                    var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 03367                    var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3368
 03369                    return string.Format(
 03370                            CultureInfo.InvariantCulture,
 03371                            "scale=trunc({0}/64)*64:trunc({1}/2)*2",
 03372                            widthParam,
 03373                            heightParam);
 3374                }
 3375
 03376                return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
 3377            }
 3378
 3379            // If Max dimensions were supplied, for width selects lowest even number between input width and width req s
 3380
 03381            if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
 3382            {
 03383                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 03384                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3385
 03386                return string.Format(
 03387                    CultureInfo.InvariantCulture,
 03388                    @"scale=trunc(min(max(iw\,ih*{3})\,min({0}\,{1}*{3}))/{2})*{2}:trunc(min(max(iw/{3}\,ih)\,min({0}/{3
 03389                    maxWidthParam,
 03390                    maxHeightParam,
 03391                    scaleVal,
 03392                    targetAr);
 3393            }
 3394
 3395            // If a fixed width was requested
 03396            if (requestedWidth.HasValue)
 3397            {
 03398                if (threedFormat.HasValue)
 3399                {
 3400                    // This method can handle 0 being passed in for the requested height
 03401                    return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0);
 3402                }
 3403
 03404                var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 3405
 03406                return string.Format(
 03407                    CultureInfo.InvariantCulture,
 03408                    "scale={0}:trunc(ow/{1}/2)*2",
 03409                    widthParam,
 03410                    targetAr);
 3411            }
 3412
 3413            // If a fixed height was requested
 03414            if (requestedHeight.HasValue)
 3415            {
 03416                var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3417
 03418                return string.Format(
 03419                    CultureInfo.InvariantCulture,
 03420                    "scale=trunc(oh*{2}/{1})*{1}:{0}",
 03421                    heightParam,
 03422                    scaleVal,
 03423                    targetAr);
 3424            }
 3425
 3426            // If a max width was requested
 03427            if (requestedMaxWidth.HasValue)
 3428            {
 03429                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 3430
 03431                return string.Format(
 03432                    CultureInfo.InvariantCulture,
 03433                    @"scale=trunc(min(max(iw\,ih*{2})\,{0})/{1})*{1}:trunc(ow/{2}/2)*2",
 03434                    maxWidthParam,
 03435                    scaleVal,
 03436                    targetAr);
 3437            }
 3438
 3439            // If a max height was requested
 03440            if (requestedMaxHeight.HasValue)
 3441            {
 03442                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3443
 03444                return string.Format(
 03445                    CultureInfo.InvariantCulture,
 03446                    @"scale=trunc(oh*{2}/{1})*{1}:min(max(iw/{2}\,ih)\,{0})",
 03447                    maxHeightParam,
 03448                    scaleVal,
 03449                    targetAr);
 3450            }
 3451
 03452            return string.Empty;
 3453        }
 3454
 3455        private static string GetFixedSwScaleFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight
 3456        {
 03457            var widthParam = requestedWidth.ToString(CultureInfo.InvariantCulture);
 03458            var heightParam = requestedHeight.ToString(CultureInfo.InvariantCulture);
 3459
 03460            string filter = null;
 3461
 03462            if (threedFormat.HasValue)
 3463            {
 03464                switch (threedFormat.Value)
 3465                {
 3466                    case Video3DFormat.HalfSideBySide:
 03467                        filter = @"crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(i
 3468                        // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars
 03469                        break;
 3470                    case Video3DFormat.FullSideBySide:
 03471                        filter = @"crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3472                        // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the s
 03473                        break;
 3474                    case Video3DFormat.HalfTopAndBottom:
 03475                        filter = @"crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(
 3476                        // htab crop height in half,scale to correct size, set the display aspect,crop out any black bar
 03477                        break;
 3478                    case Video3DFormat.FullTopAndBottom:
 03479                        filter = @"crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3480                        // ftab crop height in half, set the display aspect,crop out any black bars we may have made the
 3481                        break;
 3482                    default:
 3483                        break;
 3484                }
 3485            }
 3486
 3487            // default
 03488            if (filter is null)
 3489            {
 03490                if (requestedHeight > 0)
 3491                {
 03492                    filter = "scale=trunc({0}/2)*2:trunc({1}/2)*2";
 3493                }
 3494                else
 3495                {
 03496                    filter = "scale={0}:trunc({0}/a/2)*2";
 3497                }
 3498            }
 3499
 03500            return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam);
 3501        }
 3502
 3503        public static string GetSwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options)
 3504        {
 03505            var doubleRateDeint = options.DeinterlaceDoubleRate && state.VideoStream?.ReferenceFrameRate <= 30;
 03506            return string.Format(
 03507                CultureInfo.InvariantCulture,
 03508                "{0}={1}:-1:0",
 03509                options.DeinterlaceMethod.ToString().ToLowerInvariant(),
 03510                doubleRateDeint ? "1" : "0");
 3511        }
 3512
 3513        public string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix)
 3514        {
 03515            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03516            if (hwDeintSuffix.Contains("cuda", StringComparison.OrdinalIgnoreCase))
 3517            {
 03518                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3519
 03520                return string.Format(
 03521                    CultureInfo.InvariantCulture,
 03522                    "{0}_cuda={1}:-1:0",
 03523                    useBwdif ? "bwdif" : "yadif",
 03524                    doubleRateDeint ? "1" : "0");
 3525            }
 3526
 03527            if (hwDeintSuffix.Contains("opencl", StringComparison.OrdinalIgnoreCase))
 3528            {
 03529                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif;
 3530
 03531                if (_mediaEncoder.SupportsFilter("yadif_opencl")
 03532                    && _mediaEncoder.SupportsFilter("bwdif_opencl"))
 3533                {
 03534                    return string.Format(
 03535                        CultureInfo.InvariantCulture,
 03536                        "{0}_opencl={1}:-1:0",
 03537                        useBwdif ? "bwdif" : "yadif",
 03538                        doubleRateDeint ? "1" : "0");
 3539                }
 3540            }
 3541
 03542            if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
 3543            {
 03544                return string.Format(
 03545                    CultureInfo.InvariantCulture,
 03546                    "deinterlace_vaapi=rate={0}",
 03547                    doubleRateDeint ? "field" : "frame");
 3548            }
 3549
 03550            if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
 3551            {
 03552                return "deinterlace_qsv=mode=2";
 3553            }
 3554
 03555            if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
 3556            {
 03557                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3558
 03559                return string.Format(
 03560                    CultureInfo.InvariantCulture,
 03561                    "{0}_videotoolbox={1}:-1:0",
 03562                    useBwdif ? "bwdif" : "yadif",
 03563                    doubleRateDeint ? "1" : "0");
 3564            }
 3565
 03566            return string.Empty;
 3567        }
 3568
 3569        private string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat, bool forc
 3570        {
 03571            if (string.IsNullOrEmpty(hwTonemapSuffix))
 3572            {
 03573                return string.Empty;
 3574            }
 3575
 03576            var args = string.Empty;
 03577            var algorithm = options.TonemappingAlgorithm.ToString().ToLowerInvariant();
 03578            var mode = options.TonemappingMode.ToString().ToLowerInvariant();
 03579            var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 03580            var rangeString = range.ToString().ToLowerInvariant();
 3581
 03582            if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
 3583            {
 03584                var doVaVppProcamp = false;
 03585                var procampParams = string.Empty;
 03586                if (options.VppTonemappingBrightness != 0
 03587                    && options.VppTonemappingBrightness >= -100
 03588                    && options.VppTonemappingBrightness <= 100)
 3589                {
 03590                    procampParams += "procamp_vaapi=b={0}";
 03591                    doVaVppProcamp = true;
 3592                }
 3593
 03594                if (options.VppTonemappingContrast > 1
 03595                    && options.VppTonemappingContrast <= 10)
 3596                {
 03597                    procampParams += doVaVppProcamp ? ":c={1}" : "procamp_vaapi=c={1}";
 03598                    doVaVppProcamp = true;
 3599                }
 3600
 03601                args = procampParams + "{2}tonemap_vaapi=format={3}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
 3602
 03603                return string.Format(
 03604                        CultureInfo.InvariantCulture,
 03605                        args,
 03606                        options.VppTonemappingBrightness,
 03607                        options.VppTonemappingContrast,
 03608                        doVaVppProcamp ? "," : string.Empty,
 03609                        videoFormat ?? "nv12");
 3610            }
 3611            else
 3612            {
 03613                args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
 3614
 03615                var useLegacyTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode
 03616                                           && _legacyTonemapModes.Contains(options.TonemappingMode);
 3617
 03618                var useAdvancedTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegAdvancedTonemapMode
 03619                                              && _advancedTonemapModes.Contains(options.TonemappingMode);
 3620
 03621                if (useLegacyTonemapModes || useAdvancedTonemapModes)
 3622                {
 03623                    args += ":tonemap_mode={5}";
 3624                }
 3625
 03626                if (options.TonemappingParam != 0)
 3627                {
 03628                    args += ":param={6}";
 3629                }
 3630
 03631                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3632                {
 03633                    args += ":range={7}";
 3634                }
 3635            }
 3636
 03637            return string.Format(
 03638                    CultureInfo.InvariantCulture,
 03639                    args,
 03640                    hwTonemapSuffix,
 03641                    videoFormat ?? "nv12",
 03642                    algorithm,
 03643                    options.TonemappingPeak,
 03644                    options.TonemappingDesat,
 03645                    mode,
 03646                    options.TonemappingParam,
 03647                    rangeString);
 3648        }
 3649
 3650        private string GetLibplaceboFilter(
 3651            EncodingOptions options,
 3652            string videoFormat,
 3653            bool doTonemap,
 3654            int? videoWidth,
 3655            int? videoHeight,
 3656            int? requestedWidth,
 3657            int? requestedHeight,
 3658            int? requestedMaxWidth,
 3659            int? requestedMaxHeight,
 3660            bool forceFullRange)
 3661        {
 03662            var (outWidth, outHeight) = GetFixedOutputSize(
 03663                videoWidth,
 03664                videoHeight,
 03665                requestedWidth,
 03666                requestedHeight,
 03667                requestedMaxWidth,
 03668                requestedMaxHeight);
 3669
 03670            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03671            var isSizeFixed = !videoWidth.HasValue
 03672                || outWidth.Value != videoWidth.Value
 03673                || !videoHeight.HasValue
 03674                || outHeight.Value != videoHeight.Value;
 3675
 03676            var sizeArg = isSizeFixed ? (":w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty;
 03677            var formatArg = isFormatFixed ? (":format=" + videoFormat) : string.Empty;
 03678            var tonemapArg = string.Empty;
 3679
 03680            if (doTonemap)
 3681            {
 03682                var algorithm = options.TonemappingAlgorithm;
 03683                var algorithmString = "clip";
 03684                var mode = options.TonemappingMode;
 03685                var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 3686
 03687                if (algorithm == TonemappingAlgorithm.bt2390)
 3688                {
 03689                    algorithmString = "bt.2390";
 3690                }
 03691                else if (algorithm != TonemappingAlgorithm.none)
 3692                {
 03693                    algorithmString = algorithm.ToString().ToLowerInvariant();
 3694                }
 3695
 03696                tonemapArg = $":tonemapping={algorithmString}:peak_detect=0:color_primaries=bt709:color_trc=bt709:colors
 3697
 03698                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3699                {
 03700                    tonemapArg += ":range=" + range.ToString().ToLowerInvariant();
 3701                }
 3702            }
 3703
 03704            return string.Format(
 03705                CultureInfo.InvariantCulture,
 03706                "libplacebo=upscaler=none:downscaler=none{0}{1}{2}",
 03707                sizeArg,
 03708                formatArg,
 03709                tonemapArg);
 3710        }
 3711
 3712        public string GetVideoTransposeDirection(EncodingJobInfo state)
 3713        {
 03714            return (state.VideoStream?.Rotation ?? 0) switch
 03715            {
 03716                90 => "cclock",
 03717                180 => "reversal",
 03718                -90 => "clock",
 03719                -180 => "reversal",
 03720                _ => string.Empty
 03721            };
 3722        }
 3723
 3724        /// <summary>
 3725        /// Gets the parameter of software filter chain.
 3726        /// </summary>
 3727        /// <param name="state">Encoding state.</param>
 3728        /// <param name="options">Encoding options.</param>
 3729        /// <param name="vidEncoder">Video encoder to use.</param>
 3730        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3731        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetSwVidFilterChain(
 3732            EncodingJobInfo state,
 3733            EncodingOptions options,
 3734            string vidEncoder)
 3735        {
 03736            var inW = state.VideoStream?.Width;
 03737            var inH = state.VideoStream?.Height;
 03738            var reqW = state.BaseRequest.Width;
 03739            var reqH = state.BaseRequest.Height;
 03740            var reqMaxW = state.BaseRequest.MaxWidth;
 03741            var reqMaxH = state.BaseRequest.MaxHeight;
 03742            var threeDFormat = state.MediaSource.Video3DFormat;
 3743
 03744            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03745            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03746            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 03747            var isV4l2Encoder = vidEncoder.Contains("h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 3748
 03749            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03750            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03751            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03752            var doToneMap = IsSwTonemapAvailable(state, options);
 03753            var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI;
 3754
 03755            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03756            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03757            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 3758
 03759            var rotation = state.VideoStream?.Rotation ?? 0;
 03760            var swapWAndH = Math.Abs(rotation) == 90;
 03761            var swpInW = swapWAndH ? inH : inW;
 03762            var swpInH = swapWAndH ? inW : inH;
 3763
 3764            /* Make main filters for video stream */
 03765            var mainFilters = new List<string>();
 3766
 03767            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doToneMap));
 3768
 3769            // INPUT sw surface(memory/copy-back from vram)
 3770            // sw deint
 03771            if (doDeintH2645)
 3772            {
 03773                var deintFilter = GetSwDeinterlaceFilter(state, options);
 03774                mainFilters.Add(deintFilter);
 3775            }
 3776
 03777            var outFormat = isSwDecoder ? "yuv420p" : "nv12";
 03778            var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, r
 03779            if (isVaapiEncoder)
 3780            {
 03781                outFormat = "nv12";
 3782            }
 03783            else if (isV4l2Encoder)
 3784            {
 03785                outFormat = "yuv420p";
 3786            }
 3787
 3788            // sw scale
 03789            mainFilters.Add(swScaleFilter);
 3790
 3791            // sw tonemap
 03792            if (doToneMap)
 3793            {
 3794                // tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary
 03795                var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat;
 03796                var tonemapArgString = "tonemapx=tonemap={0}:desat={1}:peak={2}:t=bt709:m=bt709:p=bt709:format={3}";
 3797
 03798                if (options.TonemappingParam != 0)
 3799                {
 03800                    tonemapArgString += ":param={4}";
 3801                }
 3802
 03803                var range = options.TonemappingRange;
 03804                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3805                {
 03806                    tonemapArgString += ":range={5}";
 3807                }
 3808
 03809                var tonemapArgs = string.Format(
 03810                    CultureInfo.InvariantCulture,
 03811                    tonemapArgString,
 03812                    options.TonemappingAlgorithm,
 03813                    options.TonemappingDesat,
 03814                    options.TonemappingPeak,
 03815                    tonemapFormat,
 03816                    options.TonemappingParam,
 03817                    options.TonemappingRange);
 3818
 03819                mainFilters.Add(tonemapArgs);
 3820            }
 3821            else
 3822            {
 3823                // OUTPUT yuv420p/nv12 surface(memory)
 03824                mainFilters.Add("format=" + outFormat);
 3825            }
 3826
 3827            /* Make sub and overlay filters for subtitle stream */
 03828            var subFilters = new List<string>();
 03829            var overlayFilters = new List<string>();
 03830            if (hasTextSubs)
 3831            {
 3832                // subtitles=f='*.ass':alpha=0
 03833                var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03834                mainFilters.Add(textSubtitlesFilter);
 3835            }
 03836            else if (hasGraphicalSubs)
 3837            {
 03838                var subW = state.SubtitleStream?.Width;
 03839                var subH = state.SubtitleStream?.Height;
 03840                var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW
 03841                subFilters.Add(subPreProcFilters);
 03842                overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 3843            }
 3844
 03845            return (mainFilters, subFilters, overlayFilters);
 3846        }
 3847
 3848        /// <summary>
 3849        /// Gets the parameter of Nvidia NVENC filter chain.
 3850        /// </summary>
 3851        /// <param name="state">Encoding state.</param>
 3852        /// <param name="options">Encoding options.</param>
 3853        /// <param name="vidEncoder">Video encoder to use.</param>
 3854        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3855        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFilterChain(
 3856            EncodingJobInfo state,
 3857            EncodingOptions options,
 3858            string vidEncoder)
 3859        {
 03860            if (options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 3861            {
 03862                return (null, null, null);
 3863            }
 3864
 03865            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03866            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03867            var isSwEncoder = !vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 3868
 3869            // legacy cuvid pipeline(copy-back)
 03870            if ((isSwDecoder && isSwEncoder)
 03871                || !IsCudaFullSupported()
 03872                || !_mediaEncoder.SupportsFilter("alphasrc"))
 3873            {
 03874                return GetSwVidFilterChain(state, options, vidEncoder);
 3875            }
 3876
 3877            // preferred nvdec/cuvid + cuda filters + nvenc pipeline
 03878            return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 3879        }
 3880
 3881        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFiltersPrefe
 3882            EncodingJobInfo state,
 3883            EncodingOptions options,
 3884            string vidDecoder,
 3885            string vidEncoder)
 3886        {
 03887            var inW = state.VideoStream?.Width;
 03888            var inH = state.VideoStream?.Height;
 03889            var reqW = state.BaseRequest.Width;
 03890            var reqH = state.BaseRequest.Height;
 03891            var reqMaxW = state.BaseRequest.MaxWidth;
 03892            var reqMaxH = state.BaseRequest.MaxHeight;
 03893            var threeDFormat = state.MediaSource.Video3DFormat;
 3894
 03895            var isNvDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 03896            var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 03897            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03898            var isSwEncoder = !isNvencEncoder;
 03899            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 03900            var isCuInCuOut = isNvDecoder && isNvencEncoder;
 3901
 03902            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03903            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03904            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03905            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03906            var doCuTonemap = IsHwTonemapAvailable(state, options);
 3907
 03908            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03909            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03910            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 03911            var hasAssSubs = hasSubs
 03912                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 03913                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 03914            var subW = state.SubtitleStream?.Width;
 03915            var subH = state.SubtitleStream?.Height;
 3916
 03917            var rotation = state.VideoStream?.Rotation ?? 0;
 03918            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 03919            var doCuTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_cuda");
 03920            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isNvDecoder && doCuTranspose));
 03921            var swpInW = swapWAndH ? inH : inW;
 03922            var swpInH = swapWAndH ? inW : inH;
 3923
 3924            /* Make main filters for video stream */
 03925            var mainFilters = new List<string>();
 3926
 03927            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doCuTonemap));
 3928
 03929            if (isSwDecoder)
 3930            {
 3931                // INPUT sw surface(memory)
 3932                // sw deint
 03933                if (doDeintH2645)
 3934                {
 03935                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 03936                    mainFilters.Add(swDeintFilter);
 3937                }
 3938
 03939                var outFormat = doCuTonemap ? "yuv420p10le" : "yuv420p";
 03940                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 3941                // sw scale
 03942                mainFilters.Add(swScaleFilter);
 03943                mainFilters.Add($"format={outFormat}");
 3944
 3945                // sw => hw
 03946                if (doCuTonemap)
 3947                {
 03948                    mainFilters.Add("hwupload=derive_device=cuda");
 3949                }
 3950            }
 3951
 03952            if (isNvDecoder)
 3953            {
 3954                // INPUT cuda surface(vram)
 3955                // hw deint
 03956                if (doDeintH2645)
 3957                {
 03958                    var deintFilter = GetHwDeinterlaceFilter(state, options, "cuda");
 03959                    mainFilters.Add(deintFilter);
 3960                }
 3961
 3962                // hw transpose
 03963                if (doCuTranspose)
 3964                {
 03965                    mainFilters.Add($"transpose_cuda=dir={transposeDir}");
 3966                }
 3967
 03968                var isRext = IsVideoStreamHevcRext(state);
 03969                var outFormat = doCuTonemap ? (isRext ? "p010" : string.Empty) : "yuv420p";
 03970                var hwScaleFilter = GetHwScaleFilter("scale", "cuda", outFormat, false, swpInW, swpInH, reqW, reqH, reqM
 3971                // hw scale
 03972                mainFilters.Add(hwScaleFilter);
 3973            }
 3974
 3975            // hw tonemap
 03976            if (doCuTonemap)
 3977            {
 03978                var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p", isMjpegEncoder);
 03979                mainFilters.Add(tonemapFilter);
 3980            }
 3981
 03982            var memoryOutput = false;
 03983            var isUploadForCuTonemap = isSwDecoder && doCuTonemap;
 03984            if ((isNvDecoder && isSwEncoder) || (isUploadForCuTonemap && hasSubs))
 3985            {
 03986                memoryOutput = true;
 3987
 3988                // OUTPUT yuv420p surface(memory)
 03989                mainFilters.Add("hwdownload");
 03990                mainFilters.Add("format=yuv420p");
 3991            }
 3992
 3993            // OUTPUT yuv420p surface(memory)
 03994            if (isSwDecoder && isNvencEncoder && !isUploadForCuTonemap)
 3995            {
 03996                memoryOutput = true;
 3997            }
 3998
 03999            if (memoryOutput)
 4000            {
 4001                // text subtitles
 04002                if (hasTextSubs)
 4003                {
 04004                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04005                    mainFilters.Add(textSubtitlesFilter);
 4006                }
 4007            }
 4008
 4009            // OUTPUT cuda(yuv420p) surface(vram)
 4010
 4011            /* Make sub and overlay filters for subtitle stream */
 04012            var subFilters = new List<string>();
 04013            var overlayFilters = new List<string>();
 04014            if (isCuInCuOut)
 4015            {
 04016                if (hasSubs)
 4017                {
 04018                    var alphaFormatOpt = string.Empty;
 04019                    if (hasGraphicalSubs)
 4020                    {
 04021                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04022                        subFilters.Add(subPreProcFilters);
 04023                        subFilters.Add("format=yuva420p");
 4024                    }
 04025                    else if (hasTextSubs)
 4026                    {
 04027                        var framerate = state.VideoStream?.RealFrameRate;
 04028                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4029
 4030                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04031                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04032                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04033                        subFilters.Add(alphaSrcFilter);
 04034                        subFilters.Add("format=yuva420p");
 04035                        subFilters.Add(subTextSubtitlesFilter);
 4036
 04037                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayCudaAlphaFormat)
 04038                            ? ":alpha_format=premultiplied" : string.Empty;
 4039                    }
 4040
 04041                    subFilters.Add("hwupload=derive_device=cuda");
 04042                    overlayFilters.Add($"overlay_cuda=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 4043                }
 4044            }
 4045            else
 4046            {
 04047                if (hasGraphicalSubs)
 4048                {
 04049                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04050                    subFilters.Add(subPreProcFilters);
 04051                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4052                }
 4053            }
 4054
 04055            return (mainFilters, subFilters, overlayFilters);
 4056        }
 4057
 4058        /// <summary>
 4059        /// Gets the parameter of AMD AMF filter chain.
 4060        /// </summary>
 4061        /// <param name="state">Encoding state.</param>
 4062        /// <param name="options">Encoding options.</param>
 4063        /// <param name="vidEncoder">Video encoder to use.</param>
 4064        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4065        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVidFilterChain(
 4066            EncodingJobInfo state,
 4067            EncodingOptions options,
 4068            string vidEncoder)
 4069        {
 04070            if (options.HardwareAccelerationType != HardwareAccelerationType.amf)
 4071            {
 04072                return (null, null, null);
 4073            }
 4074
 04075            var isWindows = OperatingSystem.IsWindows();
 04076            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04077            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04078            var isSwEncoder = !vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04079            var isAmfDx11OclSupported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va") && IsOpenclFullSupported()
 4080
 4081            // legacy d3d11va pipeline(copy-back)
 04082            if ((isSwDecoder && isSwEncoder)
 04083                || !isAmfDx11OclSupported
 04084                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4085            {
 04086                return GetSwVidFilterChain(state, options, vidEncoder);
 4087            }
 4088
 4089            // preferred d3d11va + opencl filters + amf pipeline
 04090            return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4091        }
 4092
 4093        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdDx11VidFiltersPref
 4094            EncodingJobInfo state,
 4095            EncodingOptions options,
 4096            string vidDecoder,
 4097            string vidEncoder)
 4098        {
 04099            var inW = state.VideoStream?.Width;
 04100            var inH = state.VideoStream?.Height;
 04101            var reqW = state.BaseRequest.Width;
 04102            var reqH = state.BaseRequest.Height;
 04103            var reqMaxW = state.BaseRequest.MaxWidth;
 04104            var reqMaxH = state.BaseRequest.MaxHeight;
 04105            var threeDFormat = state.MediaSource.Video3DFormat;
 4106
 04107            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04108            var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04109            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04110            var isSwEncoder = !isAmfEncoder;
 04111            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04112            var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder;
 4113
 04114            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04115            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04116            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04117            var doOclTonemap = IsHwTonemapAvailable(state, options);
 4118
 04119            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04120            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04121            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04122            var hasAssSubs = hasSubs
 04123                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04124                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04125            var subW = state.SubtitleStream?.Width;
 04126            var subH = state.SubtitleStream?.Height;
 4127
 04128            var rotation = state.VideoStream?.Rotation ?? 0;
 04129            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04130            var doOclTranspose = !string.IsNullOrEmpty(transposeDir)
 04131                && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TransposeOpenclReversal);
 04132            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isD3d11vaDecoder && doOclTranspose));
 04133            var swpInW = swapWAndH ? inH : inW;
 04134            var swpInH = swapWAndH ? inW : inH;
 4135
 4136            /* Make main filters for video stream */
 04137            var mainFilters = new List<string>();
 4138
 04139            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 4140
 04141            if (isSwDecoder)
 4142            {
 4143                // INPUT sw surface(memory)
 4144                // sw deint
 04145                if (doDeintH2645)
 4146                {
 04147                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04148                    mainFilters.Add(swDeintFilter);
 4149                }
 4150
 04151                var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
 04152                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 4153                // sw scale
 04154                mainFilters.Add(swScaleFilter);
 04155                mainFilters.Add($"format={outFormat}");
 4156
 4157                // keep video at memory except ocl tonemap,
 4158                // since the overhead caused by hwupload >>> using sw filter.
 4159                // sw => hw
 04160                if (doOclTonemap)
 4161                {
 04162                    mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=24");
 04163                    mainFilters.Add("format=d3d11");
 04164                    mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4165                }
 4166            }
 4167
 04168            if (isD3d11vaDecoder)
 4169            {
 4170                // INPUT d3d11 surface(vram)
 4171                // map from d3d11va to opencl via d3d11-opencl interop.
 04172                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4173
 4174                // hw deint
 04175                if (doDeintH2645)
 4176                {
 04177                    var deintFilter = GetHwDeinterlaceFilter(state, options, "opencl");
 04178                    mainFilters.Add(deintFilter);
 4179                }
 4180
 4181                // hw transpose
 04182                if (doOclTranspose)
 4183                {
 04184                    mainFilters.Add($"transpose_opencl=dir={transposeDir}");
 4185                }
 4186
 04187                var outFormat = doOclTonemap ? string.Empty : "nv12";
 04188                var hwScaleFilter = GetHwScaleFilter("scale", "opencl", outFormat, false, swpInW, swpInH, reqW, reqH, re
 4189                // hw scale
 04190                mainFilters.Add(hwScaleFilter);
 4191            }
 4192
 4193            // hw tonemap
 04194            if (doOclTonemap)
 4195            {
 04196                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04197                mainFilters.Add(tonemapFilter);
 4198            }
 4199
 04200            var memoryOutput = false;
 04201            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04202            if (isD3d11vaDecoder && isSwEncoder)
 4203            {
 04204                memoryOutput = true;
 4205
 4206                // OUTPUT nv12 surface(memory)
 4207                // prefer hwmap to hwdownload on opencl.
 04208                var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap=mode=read";
 04209                mainFilters.Add(hwTransferFilter);
 04210                mainFilters.Add("format=nv12");
 4211            }
 4212
 4213            // OUTPUT yuv420p surface
 04214            if (isSwDecoder && isAmfEncoder && !isUploadForOclTonemap)
 4215            {
 04216                memoryOutput = true;
 4217            }
 4218
 04219            if (memoryOutput)
 4220            {
 4221                // text subtitles
 04222                if (hasTextSubs)
 4223                {
 04224                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04225                    mainFilters.Add(textSubtitlesFilter);
 4226                }
 4227            }
 4228
 04229            if ((isDxInDxOut || isUploadForOclTonemap) && !hasSubs)
 4230            {
 4231                // OUTPUT d3d11(nv12) surface(vram)
 4232                // reverse-mapping via d3d11-opencl interop.
 04233                mainFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04234                mainFilters.Add("format=d3d11");
 4235            }
 4236
 4237            /* Make sub and overlay filters for subtitle stream */
 04238            var subFilters = new List<string>();
 04239            var overlayFilters = new List<string>();
 04240            if (isDxInDxOut || isUploadForOclTonemap)
 4241            {
 04242                if (hasSubs)
 4243                {
 04244                    var alphaFormatOpt = string.Empty;
 04245                    if (hasGraphicalSubs)
 4246                    {
 04247                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04248                        subFilters.Add(subPreProcFilters);
 04249                        subFilters.Add("format=yuva420p");
 4250                    }
 04251                    else if (hasTextSubs)
 4252                    {
 04253                        var framerate = state.VideoStream?.RealFrameRate;
 04254                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4255
 4256                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04257                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04258                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04259                        subFilters.Add(alphaSrcFilter);
 04260                        subFilters.Add("format=yuva420p");
 04261                        subFilters.Add(subTextSubtitlesFilter);
 4262
 04263                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclAlphaForma
 04264                            ? ":alpha_format=premultiplied" : string.Empty;
 4265                    }
 4266
 04267                    subFilters.Add("hwupload=derive_device=opencl");
 04268                    overlayFilters.Add($"overlay_opencl=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 04269                    overlayFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04270                    overlayFilters.Add("format=d3d11");
 4271                }
 4272            }
 04273            else if (memoryOutput)
 4274            {
 04275                if (hasGraphicalSubs)
 4276                {
 04277                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04278                    subFilters.Add(subPreProcFilters);
 04279                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4280                }
 4281            }
 4282
 04283            return (mainFilters, subFilters, overlayFilters);
 4284        }
 4285
 4286        /// <summary>
 4287        /// Gets the parameter of Intel QSV filter chain.
 4288        /// </summary>
 4289        /// <param name="state">Encoding state.</param>
 4290        /// <param name="options">Encoding options.</param>
 4291        /// <param name="vidEncoder">Video encoder to use.</param>
 4292        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4293        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVidFilterChain(
 4294            EncodingJobInfo state,
 4295            EncodingOptions options,
 4296            string vidEncoder)
 4297        {
 04298            if (options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 4299            {
 04300                return (null, null, null);
 4301            }
 4302
 04303            var isWindows = OperatingSystem.IsWindows();
 04304            var isLinux = OperatingSystem.IsLinux();
 04305            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04306            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04307            var isSwEncoder = !vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04308            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 04309            var isIntelDx11OclSupported = isWindows
 04310                && _mediaEncoder.SupportsHwaccel("d3d11va")
 04311                && isQsvOclSupported;
 04312            var isIntelVaapiOclSupported = isLinux
 04313                && IsVaapiSupported(state)
 04314                && isQsvOclSupported;
 4315
 4316            // legacy qsv pipeline(copy-back)
 04317            if ((isSwDecoder && isSwEncoder)
 04318                || (!isIntelVaapiOclSupported && !isIntelDx11OclSupported)
 04319                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4320            {
 04321                return GetSwVidFilterChain(state, options, vidEncoder);
 4322            }
 4323
 4324            // preferred qsv(vaapi) + opencl filters pipeline
 04325            if (isIntelVaapiOclSupported)
 4326            {
 04327                return GetIntelQsvVaapiVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4328            }
 4329
 4330            // preferred qsv(d3d11) + opencl filters pipeline
 04331            if (isIntelDx11OclSupported)
 4332            {
 04333                return GetIntelQsvDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4334            }
 4335
 04336            return (null, null, null);
 4337        }
 4338
 4339        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvDx11VidFilter
 4340            EncodingJobInfo state,
 4341            EncodingOptions options,
 4342            string vidDecoder,
 4343            string vidEncoder)
 4344        {
 04345            var inW = state.VideoStream?.Width;
 04346            var inH = state.VideoStream?.Height;
 04347            var reqW = state.BaseRequest.Width;
 04348            var reqH = state.BaseRequest.Height;
 04349            var reqMaxW = state.BaseRequest.MaxWidth;
 04350            var reqMaxH = state.BaseRequest.MaxHeight;
 04351            var threeDFormat = state.MediaSource.Video3DFormat;
 4352
 04353            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04354            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04355            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04356            var isHwDecoder = isD3d11vaDecoder || isQsvDecoder;
 04357            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04358            var isSwEncoder = !isQsvEncoder;
 04359            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04360            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4361
 04362            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04363            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04364            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04365            var doVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04366            var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options);
 04367            var doTonemap = doVppTonemap || doOclTonemap;
 4368
 04369            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04370            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04371            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04372            var hasAssSubs = hasSubs
 04373                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04374                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04375            var subW = state.SubtitleStream?.Width;
 04376            var subH = state.SubtitleStream?.Height;
 4377
 04378            var rotation = state.VideoStream?.Rotation ?? 0;
 04379            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04380            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04381            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isD3d11vaDecoder || isQsvDecoder) && doVppTran
 04382            var swpInW = swapWAndH ? inH : inW;
 04383            var swpInH = swapWAndH ? inW : inH;
 4384
 4385            /* Make main filters for video stream */
 04386            var mainFilters = new List<string>();
 4387
 04388            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4389
 04390            if (isSwDecoder)
 4391            {
 4392                // INPUT sw surface(memory)
 4393                // sw deint
 04394                if (doDeintH2645)
 4395                {
 04396                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04397                    mainFilters.Add(swDeintFilter);
 4398                }
 4399
 04400                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04401                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04402                if (isMjpegEncoder && !doOclTonemap)
 4403                {
 4404                    // sw decoder + hw mjpeg encoder
 04405                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4406                }
 4407
 4408                // sw scale
 04409                mainFilters.Add(swScaleFilter);
 04410                mainFilters.Add($"format={outFormat}");
 4411
 4412                // keep video at memory except ocl tonemap,
 4413                // since the overhead caused by hwupload >>> using sw filter.
 4414                // sw => hw
 04415                if (doOclTonemap)
 4416                {
 04417                    mainFilters.Add("hwupload=derive_device=opencl");
 4418                }
 4419            }
 04420            else if (isD3d11vaDecoder || isQsvDecoder)
 4421            {
 04422                var isRext = IsVideoStreamHevcRext(state);
 04423                var twoPassVppTonemap = false;
 04424                var doVppFullRangeOut = isMjpegEncoder
 04425                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04426                var doVppScaleModeHq = isMjpegEncoder
 04427                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 04428                var doVppProcamp = false;
 04429                var procampParams = string.Empty;
 04430                var procampParamsString = string.Empty;
 04431                if (doVppTonemap)
 4432                {
 04433                    if (isRext)
 4434                    {
 4435                        // VPP tonemap requires p010 input
 04436                        twoPassVppTonemap = true;
 4437                    }
 4438
 04439                    if (options.VppTonemappingBrightness != 0
 04440                        && options.VppTonemappingBrightness >= -100
 04441                        && options.VppTonemappingBrightness <= 100)
 4442                    {
 04443                        procampParamsString += ":brightness={0}";
 04444                        twoPassVppTonemap = doVppProcamp = true;
 4445                    }
 4446
 04447                    if (options.VppTonemappingContrast > 1
 04448                        && options.VppTonemappingContrast <= 10)
 4449                    {
 04450                        procampParamsString += ":contrast={1}";
 04451                        twoPassVppTonemap = doVppProcamp = true;
 4452                    }
 4453
 04454                    if (doVppProcamp)
 4455                    {
 04456                        procampParamsString += ":procamp=1:async_depth=2";
 04457                        procampParams = string.Format(
 04458                            CultureInfo.InvariantCulture,
 04459                            procampParamsString,
 04460                            options.VppTonemappingBrightness,
 04461                            options.VppTonemappingContrast);
 4462                    }
 4463                }
 4464
 04465                var outFormat = doOclTonemap ? ((doVppTranspose || isRext) ? "p010" : string.Empty) : "nv12";
 04466                outFormat = twoPassVppTonemap ? "p010" : outFormat;
 4467
 04468                var swapOutputWandH = doVppTranspose && swapWAndH;
 04469                var hwScaleFilter = GetHwScaleFilter("vpp", "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, req
 4470
 4471                // d3d11va doesn't support dynamic pool size, use vpp filter ctx to relay
 4472                // to prevent encoder async and bframes from exhausting the decoder pool.
 04473                if (!string.IsNullOrEmpty(hwScaleFilter) && isD3d11vaDecoder)
 4474                {
 04475                    hwScaleFilter += ":passthrough=0";
 4476                }
 4477
 04478                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose)
 4479                {
 04480                    hwScaleFilter += $":transpose={transposeDir}";
 4481                }
 4482
 04483                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4484                {
 04485                    hwScaleFilter += (doVppFullRangeOut && !doOclTonemap) ? ":out_range=pc" : string.Empty;
 04486                    hwScaleFilter += doVppScaleModeHq ? ":scale_mode=hq" : string.Empty;
 4487                }
 4488
 04489                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTonemap)
 4490                {
 04491                    hwScaleFilter += doVppProcamp ? procampParams : (twoPassVppTonemap ? string.Empty : ":tonemap=1");
 4492                }
 4493
 04494                if (isD3d11vaDecoder)
 4495                {
 04496                    if (!string.IsNullOrEmpty(hwScaleFilter) || doDeintH2645)
 4497                    {
 4498                        // INPUT d3d11 surface(vram)
 4499                        // map from d3d11va to qsv.
 04500                        mainFilters.Add("hwmap=derive_device=qsv");
 4501                    }
 4502                }
 4503
 4504                // hw deint
 04505                if (doDeintH2645)
 4506                {
 04507                    var deintFilter = GetHwDeinterlaceFilter(state, options, "qsv");
 04508                    mainFilters.Add(deintFilter);
 4509                }
 4510
 4511                // hw transpose & scale & tonemap(w/o procamp)
 04512                mainFilters.Add(hwScaleFilter);
 4513
 4514                // hw tonemap(w/ procamp)
 04515                if (doVppTonemap && twoPassVppTonemap)
 4516                {
 04517                    mainFilters.Add("vpp_qsv=tonemap=1:format=nv12:async_depth=2");
 4518                }
 4519
 4520                // force bt709 just in case vpp tonemap is not triggered or using MSDK instead of VPL.
 04521                if (doVppTonemap)
 4522                {
 04523                    mainFilters.Add(GetOverwriteColorPropertiesParam(state, false));
 4524                }
 4525            }
 4526
 04527            if (doOclTonemap && isHwDecoder)
 4528            {
 4529                // map from qsv to opencl via qsv(d3d11)-opencl interop.
 04530                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4531            }
 4532
 4533            // hw tonemap
 04534            if (doOclTonemap)
 4535            {
 04536                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04537                mainFilters.Add(tonemapFilter);
 4538            }
 4539
 04540            var memoryOutput = false;
 04541            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04542            var isHwmapUsable = isSwEncoder && doOclTonemap;
 04543            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4544            {
 04545                memoryOutput = true;
 4546
 4547                // OUTPUT nv12 surface(memory)
 4548                // prefer hwmap to hwdownload on opencl.
 4549                // qsv hwmap is not fully implemented for the time being.
 04550                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04551                mainFilters.Add("format=nv12");
 4552            }
 4553
 4554            // OUTPUT nv12 surface(memory)
 04555            if (isSwDecoder && isQsvEncoder)
 4556            {
 04557                memoryOutput = true;
 4558            }
 4559
 04560            if (memoryOutput)
 4561            {
 4562                // text subtitles
 04563                if (hasTextSubs)
 4564                {
 04565                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04566                    mainFilters.Add(textSubtitlesFilter);
 4567                }
 4568            }
 4569
 04570            if (isQsvInQsvOut && doOclTonemap)
 4571            {
 4572                // OUTPUT qsv(nv12) surface(vram)
 4573                // reverse-mapping via qsv(d3d11)-opencl interop.
 04574                mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1");
 04575                mainFilters.Add("format=qsv");
 4576            }
 4577
 4578            /* Make sub and overlay filters for subtitle stream */
 04579            var subFilters = new List<string>();
 04580            var overlayFilters = new List<string>();
 04581            if (isQsvInQsvOut)
 4582            {
 04583                if (hasSubs)
 4584                {
 04585                    if (hasGraphicalSubs)
 4586                    {
 4587                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04588                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04589                        subFilters.Add(subPreProcFilters);
 04590                        subFilters.Add("format=bgra");
 4591                    }
 04592                    else if (hasTextSubs)
 4593                    {
 04594                        var framerate = state.VideoStream?.RealFrameRate;
 04595                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4596
 4597                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 04598                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04599                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04600                        subFilters.Add(alphaSrcFilter);
 04601                        subFilters.Add("format=bgra");
 04602                        subFilters.Add(subTextSubtitlesFilter);
 4603                    }
 4604
 4605                    // qsv requires a fixed pool size.
 4606                    // default to 64 otherwise it will fail on certain iGPU.
 04607                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4608
 04609                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04610                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04611                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04612                        : string.Empty;
 04613                    var overlayQsvFilter = string.Format(
 04614                        CultureInfo.InvariantCulture,
 04615                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04616                        overlaySize);
 04617                    overlayFilters.Add(overlayQsvFilter);
 4618                }
 4619            }
 04620            else if (memoryOutput)
 4621            {
 04622                if (hasGraphicalSubs)
 4623                {
 04624                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04625                    subFilters.Add(subPreProcFilters);
 04626                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4627                }
 4628            }
 4629
 04630            return (mainFilters, subFilters, overlayFilters);
 4631        }
 4632
 4633        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvVaapiVidFilte
 4634            EncodingJobInfo state,
 4635            EncodingOptions options,
 4636            string vidDecoder,
 4637            string vidEncoder)
 4638        {
 04639            var inW = state.VideoStream?.Width;
 04640            var inH = state.VideoStream?.Height;
 04641            var reqW = state.BaseRequest.Width;
 04642            var reqH = state.BaseRequest.Height;
 04643            var reqMaxW = state.BaseRequest.MaxWidth;
 04644            var reqMaxH = state.BaseRequest.MaxHeight;
 04645            var threeDFormat = state.MediaSource.Video3DFormat;
 4646
 04647            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04648            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04649            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04650            var isHwDecoder = isVaapiDecoder || isQsvDecoder;
 04651            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04652            var isSwEncoder = !isQsvEncoder;
 04653            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04654            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4655
 04656            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04657            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04658            var doVaVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04659            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04660            var doTonemap = doVaVppTonemap || doOclTonemap;
 04661            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4662
 04663            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04664            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04665            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04666            var hasAssSubs = hasSubs
 04667                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04668                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04669            var subW = state.SubtitleStream?.Width;
 04670            var subH = state.SubtitleStream?.Height;
 4671
 04672            var rotation = state.VideoStream?.Rotation ?? 0;
 04673            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04674            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04675            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isVaapiDecoder || isQsvDecoder) && doVppTransp
 04676            var swpInW = swapWAndH ? inH : inW;
 04677            var swpInH = swapWAndH ? inW : inH;
 4678
 4679            /* Make main filters for video stream */
 04680            var mainFilters = new List<string>();
 4681
 04682            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4683
 04684            if (isSwDecoder)
 4685            {
 4686                // INPUT sw surface(memory)
 4687                // sw deint
 04688                if (doDeintH2645)
 4689                {
 04690                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04691                    mainFilters.Add(swDeintFilter);
 4692                }
 4693
 04694                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04695                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04696                if (isMjpegEncoder && !doOclTonemap)
 4697                {
 4698                    // sw decoder + hw mjpeg encoder
 04699                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4700                }
 4701
 4702                // sw scale
 04703                mainFilters.Add(swScaleFilter);
 04704                mainFilters.Add($"format={outFormat}");
 4705
 4706                // keep video at memory except ocl tonemap,
 4707                // since the overhead caused by hwupload >>> using sw filter.
 4708                // sw => hw
 04709                if (doOclTonemap)
 4710                {
 04711                    mainFilters.Add("hwupload=derive_device=opencl");
 4712                }
 4713            }
 04714            else if (isVaapiDecoder || isQsvDecoder)
 4715            {
 04716                var hwFilterSuffix = isVaapiDecoder ? "vaapi" : "qsv";
 04717                var isRext = IsVideoStreamHevcRext(state);
 04718                var doVppFullRangeOut = isMjpegEncoder
 04719                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04720                var doVppScaleModeHq = isMjpegEncoder
 04721                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 4722
 4723                // INPUT vaapi/qsv surface(vram)
 4724                // hw deint
 04725                if (doDeintH2645)
 4726                {
 04727                    var deintFilter = GetHwDeinterlaceFilter(state, options, hwFilterSuffix);
 04728                    mainFilters.Add(deintFilter);
 4729                }
 4730
 4731                // hw transpose(vaapi vpp)
 04732                if (isVaapiDecoder && doVppTranspose)
 4733                {
 04734                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 4735                }
 4736
 04737                var outFormat = doTonemap ? (((isQsvDecoder && doVppTranspose) || isRext) ? "p010" : string.Empty) : "nv
 04738                var swapOutputWandH = isQsvDecoder && doVppTranspose && swapWAndH;
 04739                var hwScalePrefix = isQsvDecoder ? "vpp" : "scale";
 04740                var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, hwFilterSuffix, outFormat, swapOutputWandH, swpInW, 
 4741
 04742                if (!string.IsNullOrEmpty(hwScaleFilter) && isQsvDecoder && doVppTranspose)
 4743                {
 04744                    hwScaleFilter += $":transpose={transposeDir}";
 4745                }
 4746
 04747                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4748                {
 04749                    hwScaleFilter += ((isQsvDecoder && !doVppFullRangeOut) || doOclTonemap) ? string.Empty : ":out_range
 04750                    hwScaleFilter += isQsvDecoder ? (doVppScaleModeHq ? ":scale_mode=hq" : string.Empty) : ":mode=hq";
 4751                }
 4752
 4753                // allocate extra pool sizes for vaapi vpp scale
 04754                if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder)
 4755                {
 04756                    hwScaleFilter += ":extra_hw_frames=24";
 4757                }
 4758
 4759                // hw transpose(qsv vpp) & scale
 04760                mainFilters.Add(hwScaleFilter);
 4761            }
 4762
 4763            // vaapi vpp tonemap
 04764            if (doVaVppTonemap && isHwDecoder)
 4765            {
 04766                if (isQsvDecoder)
 4767                {
 4768                    // map from qsv to vaapi.
 04769                    mainFilters.Add("hwmap=derive_device=vaapi");
 04770                    mainFilters.Add("format=vaapi");
 4771                }
 4772
 04773                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 04774                mainFilters.Add(tonemapFilter);
 4775
 04776                if (isQsvDecoder)
 4777                {
 4778                    // map from vaapi to qsv.
 04779                    mainFilters.Add("hwmap=derive_device=qsv");
 04780                    mainFilters.Add("format=qsv");
 4781                }
 4782            }
 4783
 04784            if (doOclTonemap && isHwDecoder)
 4785            {
 4786                // map from qsv to opencl via qsv(vaapi)-opencl interop.
 04787                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4788            }
 4789
 4790            // ocl tonemap
 04791            if (doOclTonemap)
 4792            {
 04793                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04794                mainFilters.Add(tonemapFilter);
 4795            }
 4796
 04797            var memoryOutput = false;
 04798            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04799            var isHwmapUsable = isSwEncoder && (doOclTonemap || isVaapiDecoder);
 04800            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4801            {
 04802                memoryOutput = true;
 4803
 4804                // OUTPUT nv12 surface(memory)
 4805                // prefer hwmap to hwdownload on opencl/vaapi.
 4806                // qsv hwmap is not fully implemented for the time being.
 04807                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04808                mainFilters.Add("format=nv12");
 4809            }
 4810
 4811            // OUTPUT nv12 surface(memory)
 04812            if (isSwDecoder && isQsvEncoder)
 4813            {
 04814                memoryOutput = true;
 4815            }
 4816
 04817            if (memoryOutput)
 4818            {
 4819                // text subtitles
 04820                if (hasTextSubs)
 4821                {
 04822                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04823                    mainFilters.Add(textSubtitlesFilter);
 4824                }
 4825            }
 4826
 04827            if (isQsvInQsvOut)
 4828            {
 04829                if (doOclTonemap)
 4830                {
 4831                    // OUTPUT qsv(nv12) surface(vram)
 4832                    // reverse-mapping via qsv(vaapi)-opencl interop.
 4833                    // add extra pool size to avoid the 'cannot allocate memory' error on hevc_qsv.
 04834                    mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1:extra_hw_frames=16");
 04835                    mainFilters.Add("format=qsv");
 4836                }
 04837                else if (isVaapiDecoder)
 4838                {
 04839                    mainFilters.Add("hwmap=derive_device=qsv");
 04840                    mainFilters.Add("format=qsv");
 4841                }
 4842            }
 4843
 4844            /* Make sub and overlay filters for subtitle stream */
 04845            var subFilters = new List<string>();
 04846            var overlayFilters = new List<string>();
 04847            if (isQsvInQsvOut)
 4848            {
 04849                if (hasSubs)
 4850                {
 04851                    if (hasGraphicalSubs)
 4852                    {
 4853                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04854                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04855                        subFilters.Add(subPreProcFilters);
 04856                        subFilters.Add("format=bgra");
 4857                    }
 04858                    else if (hasTextSubs)
 4859                    {
 04860                        var framerate = state.VideoStream?.RealFrameRate;
 04861                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4862
 04863                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04864                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04865                        subFilters.Add(alphaSrcFilter);
 04866                        subFilters.Add("format=bgra");
 04867                        subFilters.Add(subTextSubtitlesFilter);
 4868                    }
 4869
 4870                    // qsv requires a fixed pool size.
 4871                    // default to 64 otherwise it will fail on certain iGPU.
 04872                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4873
 04874                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04875                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04876                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04877                        : string.Empty;
 04878                    var overlayQsvFilter = string.Format(
 04879                        CultureInfo.InvariantCulture,
 04880                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04881                        overlaySize);
 04882                    overlayFilters.Add(overlayQsvFilter);
 4883                }
 4884            }
 04885            else if (memoryOutput)
 4886            {
 04887                if (hasGraphicalSubs)
 4888                {
 04889                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04890                    subFilters.Add(subPreProcFilters);
 04891                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4892                }
 4893            }
 4894
 04895            return (mainFilters, subFilters, overlayFilters);
 4896        }
 4897
 4898        /// <summary>
 4899        /// Gets the parameter of Intel/AMD VAAPI filter chain.
 4900        /// </summary>
 4901        /// <param name="state">Encoding state.</param>
 4902        /// <param name="options">Encoding options.</param>
 4903        /// <param name="vidEncoder">Video encoder to use.</param>
 4904        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4905        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiVidFilterChain(
 4906            EncodingJobInfo state,
 4907            EncodingOptions options,
 4908            string vidEncoder)
 4909        {
 04910            if (options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 4911            {
 04912                return (null, null, null);
 4913            }
 4914
 04915            var isLinux = OperatingSystem.IsLinux();
 04916            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04917            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04918            var isSwEncoder = !vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04919            var isVaapiFullSupported = isLinux && IsVaapiSupported(state) && IsVaapiFullSupported();
 04920            var isVaapiOclSupported = isVaapiFullSupported && IsOpenclFullSupported();
 04921            var isVaapiVkSupported = isVaapiFullSupported && IsVulkanFullSupported();
 4922
 4923            // legacy vaapi pipeline(copy-back)
 04924            if ((isSwDecoder && isSwEncoder)
 04925                || !isVaapiOclSupported
 04926                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4927            {
 04928                var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
 4929
 04930                if (!isSwEncoder)
 4931                {
 04932                    var newfilters = new List<string>();
 04933                    var noOverlay = swFilterChain.OverlayFilters.Count == 0;
 04934                    newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
 04935                    newfilters.Add("hwupload=derive_device=vaapi");
 4936
 04937                    var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
 04938                    var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
 04939                    return (mainFilters, swFilterChain.SubFilters, overlayFilters);
 4940                }
 4941
 04942                return swFilterChain;
 4943            }
 4944
 4945            // preferred vaapi + opencl filters pipeline
 04946            if (_mediaEncoder.IsVaapiDeviceInteliHD)
 4947            {
 4948                // Intel iHD path, with extra vpp tonemap and overlay support.
 04949                return GetIntelVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4950            }
 4951
 4952            // preferred vaapi + vulkan filters pipeline
 04953            if (_mediaEncoder.IsVaapiDeviceAmd
 04954                && isVaapiVkSupported
 04955                && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 04956                && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 4957            {
 4958                // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support.
 04959                return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4960            }
 4961
 4962            // Intel i965 and Amd legacy driver path, only featuring scale and deinterlace support.
 04963            return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4964        }
 4965
 4966        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVaapiFullVidFilt
 4967            EncodingJobInfo state,
 4968            EncodingOptions options,
 4969            string vidDecoder,
 4970            string vidEncoder)
 4971        {
 04972            var inW = state.VideoStream?.Width;
 04973            var inH = state.VideoStream?.Height;
 04974            var reqW = state.BaseRequest.Width;
 04975            var reqH = state.BaseRequest.Height;
 04976            var reqMaxW = state.BaseRequest.MaxWidth;
 04977            var reqMaxH = state.BaseRequest.MaxHeight;
 04978            var threeDFormat = state.MediaSource.Video3DFormat;
 4979
 04980            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04981            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04982            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04983            var isSwEncoder = !isVaapiEncoder;
 04984            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04985            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 4986
 04987            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04988            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04989            var doVaVppTonemap = isVaapiDecoder && IsIntelVppTonemapAvailable(state, options);
 04990            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04991            var doTonemap = doVaVppTonemap || doOclTonemap;
 04992            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4993
 04994            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04995            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04996            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04997            var hasAssSubs = hasSubs
 04998                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04999                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 05000            var subW = state.SubtitleStream?.Width;
 05001            var subH = state.SubtitleStream?.Height;
 5002
 05003            var rotation = state.VideoStream?.Rotation ?? 0;
 05004            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05005            var doVaVppTranspose = !string.IsNullOrEmpty(transposeDir);
 05006            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVaVppTranspose));
 05007            var swpInW = swapWAndH ? inH : inW;
 05008            var swpInH = swapWAndH ? inW : inH;
 5009
 5010            /* Make main filters for video stream */
 05011            var mainFilters = new List<string>();
 5012
 05013            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 5014
 05015            if (isSwDecoder)
 5016            {
 5017                // INPUT sw surface(memory)
 5018                // sw deint
 05019                if (doDeintH2645)
 5020                {
 05021                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05022                    mainFilters.Add(swDeintFilter);
 5023                }
 5024
 05025                var outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05026                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05027                if (isMjpegEncoder && !doOclTonemap)
 5028                {
 5029                    // sw decoder + hw mjpeg encoder
 05030                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5031                }
 5032
 5033                // sw scale
 05034                mainFilters.Add(swScaleFilter);
 05035                mainFilters.Add($"format={outFormat}");
 5036
 5037                // keep video at memory except ocl tonemap,
 5038                // since the overhead caused by hwupload >>> using sw filter.
 5039                // sw => hw
 05040                if (doOclTonemap)
 5041                {
 05042                    mainFilters.Add("hwupload=derive_device=opencl");
 5043                }
 5044            }
 05045            else if (isVaapiDecoder)
 5046            {
 05047                var isRext = IsVideoStreamHevcRext(state);
 5048
 5049                // INPUT vaapi surface(vram)
 5050                // hw deint
 05051                if (doDeintH2645)
 5052                {
 05053                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05054                    mainFilters.Add(deintFilter);
 5055                }
 5056
 5057                // hw transpose
 05058                if (doVaVppTranspose)
 5059                {
 05060                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 5061                }
 5062
 05063                var outFormat = doTonemap ? (isRext ? "p010" : string.Empty) : "nv12";
 05064                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, swpInW, swpInH, reqW, reqH, req
 5065
 05066                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5067                {
 05068                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05069                    hwScaleFilter += ":mode=hq";
 5070                }
 5071
 5072                // allocate extra pool sizes for vaapi vpp
 05073                if (!string.IsNullOrEmpty(hwScaleFilter))
 5074                {
 05075                    hwScaleFilter += ":extra_hw_frames=24";
 5076                }
 5077
 5078                // hw scale
 05079                mainFilters.Add(hwScaleFilter);
 5080            }
 5081
 5082            // vaapi vpp tonemap
 05083            if (doVaVppTonemap && isVaapiDecoder)
 5084            {
 05085                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 05086                mainFilters.Add(tonemapFilter);
 5087            }
 5088
 05089            if (doOclTonemap && isVaapiDecoder)
 5090            {
 5091                // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05092                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 5093            }
 5094
 5095            // ocl tonemap
 05096            if (doOclTonemap)
 5097            {
 05098                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05099                mainFilters.Add(tonemapFilter);
 5100            }
 5101
 05102            if (doOclTonemap && isVaInVaOut)
 5103            {
 5104                // OUTPUT vaapi(nv12) surface(vram)
 5105                // reverse-mapping via vaapi-opencl interop.
 05106                mainFilters.Add("hwmap=derive_device=vaapi:mode=write:reverse=1");
 05107                mainFilters.Add("format=vaapi");
 5108            }
 5109
 05110            var memoryOutput = false;
 05111            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 05112            var isHwmapNotUsable = isUploadForOclTonemap && isVaapiEncoder;
 05113            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap)
 5114            {
 05115                memoryOutput = true;
 5116
 5117                // OUTPUT nv12 surface(memory)
 5118                // prefer hwmap to hwdownload on opencl/vaapi.
 05119                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap=mode=read");
 05120                mainFilters.Add("format=nv12");
 5121            }
 5122
 5123            // OUTPUT nv12 surface(memory)
 05124            if (isSwDecoder && isVaapiEncoder)
 5125            {
 05126                memoryOutput = true;
 5127            }
 5128
 05129            if (memoryOutput)
 5130            {
 5131                // text subtitles
 05132                if (hasTextSubs)
 5133                {
 05134                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05135                    mainFilters.Add(textSubtitlesFilter);
 5136                }
 5137            }
 5138
 05139            if (memoryOutput && isVaapiEncoder)
 5140            {
 05141                if (!hasGraphicalSubs)
 5142                {
 05143                    mainFilters.Add("hwupload_vaapi");
 5144                }
 5145            }
 5146
 5147            /* Make sub and overlay filters for subtitle stream */
 05148            var subFilters = new List<string>();
 05149            var overlayFilters = new List<string>();
 05150            if (isVaInVaOut)
 5151            {
 05152                if (hasSubs)
 5153                {
 05154                    if (hasGraphicalSubs)
 5155                    {
 5156                        // overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead
 05157                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 05158                        subFilters.Add(subPreProcFilters);
 05159                        subFilters.Add("format=bgra");
 5160                    }
 05161                    else if (hasTextSubs)
 5162                    {
 05163                        var framerate = state.VideoStream?.RealFrameRate;
 05164                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5165
 05166                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 05167                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05168                        subFilters.Add(alphaSrcFilter);
 05169                        subFilters.Add("format=bgra");
 05170                        subFilters.Add(subTextSubtitlesFilter);
 5171                    }
 5172
 05173                    subFilters.Add("hwupload=derive_device=vaapi");
 5174
 05175                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 05176                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 05177                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 05178                        : string.Empty;
 05179                    var overlayVaapiFilter = string.Format(
 05180                        CultureInfo.InvariantCulture,
 05181                        "overlay_vaapi=eof_action=pass:repeatlast=0{0}",
 05182                        overlaySize);
 05183                    overlayFilters.Add(overlayVaapiFilter);
 5184                }
 5185            }
 05186            else if (memoryOutput)
 5187            {
 05188                if (hasGraphicalSubs)
 5189                {
 05190                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05191                    subFilters.Add(subPreProcFilters);
 05192                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5193
 05194                    if (isVaapiEncoder)
 5195                    {
 05196                        overlayFilters.Add("hwupload_vaapi");
 5197                    }
 5198                }
 5199            }
 5200
 05201            return (mainFilters, subFilters, overlayFilters);
 5202        }
 5203
 5204        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVaapiFullVidFilter
 5205            EncodingJobInfo state,
 5206            EncodingOptions options,
 5207            string vidDecoder,
 5208            string vidEncoder)
 5209        {
 05210            var inW = state.VideoStream?.Width;
 05211            var inH = state.VideoStream?.Height;
 05212            var reqW = state.BaseRequest.Width;
 05213            var reqH = state.BaseRequest.Height;
 05214            var reqMaxW = state.BaseRequest.MaxWidth;
 05215            var reqMaxH = state.BaseRequest.MaxHeight;
 05216            var threeDFormat = state.MediaSource.Video3DFormat;
 5217
 05218            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05219            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05220            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05221            var isSwEncoder = !isVaapiEncoder;
 05222            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5223
 05224            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05225            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05226            var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
 05227            var doDeintH2645 = doDeintH264 || doDeintHevc;
 5228
 05229            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05230            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05231            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05232            var hasAssSubs = hasSubs
 05233                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05234                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5235
 05236            var rotation = state.VideoStream?.Rotation ?? 0;
 05237            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05238            var doVkTranspose = isVaapiDecoder && !string.IsNullOrEmpty(transposeDir);
 05239            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVkTranspose));
 05240            var swpInW = swapWAndH ? inH : inW;
 05241            var swpInH = swapWAndH ? inW : inH;
 5242
 5243            /* Make main filters for video stream */
 05244            var mainFilters = new List<string>();
 5245
 05246            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doVkTonemap));
 5247
 05248            if (isSwDecoder)
 5249            {
 5250                // INPUT sw surface(memory)
 5251                // sw deint
 05252                if (doDeintH2645)
 5253                {
 05254                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05255                    mainFilters.Add(swDeintFilter);
 5256                }
 5257
 05258                if (doVkTonemap || hasSubs)
 5259                {
 5260                    // sw => hw
 05261                    mainFilters.Add("hwupload=derive_device=vulkan");
 05262                    mainFilters.Add("format=vulkan");
 5263                }
 5264                else
 5265                {
 5266                    // sw scale
 05267                    var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW,
 05268                    mainFilters.Add(swScaleFilter);
 05269                    mainFilters.Add("format=nv12");
 5270                }
 5271            }
 05272            else if (isVaapiDecoder)
 5273            {
 5274                // INPUT vaapi surface(vram)
 05275                if (doVkTranspose || doVkTonemap || hasSubs)
 5276                {
 5277                    // map from vaapi to vulkan/drm via interop (Polaris/gfx8+).
 05278                    if (_mediaEncoder.EncoderVersion >= _minFFmpegAlteredVaVkInterop)
 5279                    {
 05280                        if (doVkTranspose || !_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5281                        {
 5282                            // disable the indirect va-drm-vk mapping since it's no longer reliable.
 05283                            mainFilters.Add("hwmap=derive_device=drm");
 05284                            mainFilters.Add("format=drm_prime");
 05285                            mainFilters.Add("hwmap=derive_device=vulkan");
 05286                            mainFilters.Add("format=vulkan");
 5287
 5288                            // workaround for libplacebo using the imported vulkan frame on gfx8.
 05289                            if (!_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5290                            {
 05291                                mainFilters.Add("scale_vulkan");
 5292                            }
 5293                        }
 05294                        else if (doVkTonemap || hasSubs)
 5295                        {
 5296                            // non ad-hoc libplacebo also accepts drm_prime direct input.
 05297                            mainFilters.Add("hwmap=derive_device=drm");
 05298                            mainFilters.Add("format=drm_prime");
 5299                        }
 5300                    }
 5301                    else // legacy va-vk mapping that works only in jellyfin-ffmpeg6
 5302                    {
 05303                        mainFilters.Add("hwmap=derive_device=vulkan");
 05304                        mainFilters.Add("format=vulkan");
 5305                    }
 5306                }
 5307                else
 5308                {
 5309                    // hw deint
 05310                    if (doDeintH2645)
 5311                    {
 05312                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05313                        mainFilters.Add(deintFilter);
 5314                    }
 5315
 5316                    // hw scale
 05317                    var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", "nv12", false, inW, inH, reqW, reqH, reqMaxW,
 5318
 05319                    if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder && !doVkTonemap)
 5320                    {
 05321                        hwScaleFilter += ":out_range=pc:mode=hq";
 5322                    }
 5323
 05324                    mainFilters.Add(hwScaleFilter);
 5325                }
 5326            }
 5327
 5328            // vk transpose
 05329            if (doVkTranspose)
 5330            {
 05331                if (string.Equals(transposeDir, "reversal", StringComparison.OrdinalIgnoreCase))
 5332                {
 05333                    mainFilters.Add("flip_vulkan");
 5334                }
 5335                else
 5336                {
 05337                    mainFilters.Add($"transpose_vulkan=dir={transposeDir}");
 5338                }
 5339            }
 5340
 5341            // vk libplacebo
 05342            if (doVkTonemap || hasSubs)
 5343            {
 05344                var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, req
 05345                mainFilters.Add(libplaceboFilter);
 05346                mainFilters.Add("format=vulkan");
 5347            }
 5348
 05349            if (doVkTonemap && !hasSubs)
 5350            {
 5351                // OUTPUT vaapi(nv12) surface(vram)
 5352                // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05353                mainFilters.Add("hwmap=derive_device=vaapi");
 05354                mainFilters.Add("format=vaapi");
 5355
 5356                // clear the surf->meta_offset and output nv12
 05357                mainFilters.Add("scale_vaapi=format=nv12");
 5358
 5359                // hw deint
 05360                if (doDeintH2645)
 5361                {
 05362                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05363                    mainFilters.Add(deintFilter);
 5364                }
 5365            }
 5366
 05367            if (!hasSubs)
 5368            {
 5369                // OUTPUT nv12 surface(memory)
 05370                if (isSwEncoder && (doVkTonemap || isVaapiDecoder))
 5371                {
 05372                    mainFilters.Add("hwdownload");
 05373                    mainFilters.Add("format=nv12");
 5374                }
 5375
 05376                if (isSwDecoder && isVaapiEncoder && !doVkTonemap)
 5377                {
 05378                    mainFilters.Add("hwupload_vaapi");
 5379                }
 5380            }
 5381
 5382            /* Make sub and overlay filters for subtitle stream */
 05383            var subFilters = new List<string>();
 05384            var overlayFilters = new List<string>();
 05385            if (hasSubs)
 5386            {
 05387                if (hasGraphicalSubs)
 5388                {
 05389                    var subW = state.SubtitleStream?.Width;
 05390                    var subH = state.SubtitleStream?.Height;
 05391                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05392                    subFilters.Add(subPreProcFilters);
 05393                    subFilters.Add("format=bgra");
 5394                }
 05395                else if (hasTextSubs)
 5396                {
 05397                    var framerate = state.VideoStream?.RealFrameRate;
 05398                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5399
 05400                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05401                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05402                    subFilters.Add(alphaSrcFilter);
 05403                    subFilters.Add("format=bgra");
 05404                    subFilters.Add(subTextSubtitlesFilter);
 5405                }
 5406
 05407                subFilters.Add("hwupload=derive_device=vulkan");
 05408                subFilters.Add("format=vulkan");
 5409
 05410                overlayFilters.Add("overlay_vulkan=eof_action=pass:repeatlast=0");
 5411
 05412                if (isSwEncoder)
 5413                {
 5414                    // OUTPUT nv12 surface(memory)
 05415                    overlayFilters.Add("scale_vulkan=format=nv12");
 05416                    overlayFilters.Add("hwdownload");
 05417                    overlayFilters.Add("format=nv12");
 5418                }
 05419                else if (isVaapiEncoder)
 5420                {
 5421                    // OUTPUT vaapi(nv12) surface(vram)
 5422                    // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05423                    overlayFilters.Add("hwmap=derive_device=vaapi");
 05424                    overlayFilters.Add("format=vaapi");
 5425
 5426                    // clear the surf->meta_offset and output nv12
 05427                    overlayFilters.Add("scale_vaapi=format=nv12");
 5428
 5429                    // hw deint
 05430                    if (doDeintH2645)
 5431                    {
 05432                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05433                        overlayFilters.Add(deintFilter);
 5434                    }
 5435                }
 5436            }
 5437
 05438            return (mainFilters, subFilters, overlayFilters);
 5439        }
 5440
 5441        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiLimitedVidFilter
 5442            EncodingJobInfo state,
 5443            EncodingOptions options,
 5444            string vidDecoder,
 5445            string vidEncoder)
 5446        {
 05447            var inW = state.VideoStream?.Width;
 05448            var inH = state.VideoStream?.Height;
 05449            var reqW = state.BaseRequest.Width;
 05450            var reqH = state.BaseRequest.Height;
 05451            var reqMaxW = state.BaseRequest.MaxWidth;
 05452            var reqMaxH = state.BaseRequest.MaxHeight;
 05453            var threeDFormat = state.MediaSource.Video3DFormat;
 5454
 05455            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05456            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05457            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05458            var isSwEncoder = !isVaapiEncoder;
 05459            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05460            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 05461            var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965;
 05462            var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd;
 5463
 05464            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05465            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05466            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05467            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5468
 05469            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05470            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05471            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 5472
 05473            var rotation = state.VideoStream?.Rotation ?? 0;
 05474            var swapWAndH = Math.Abs(rotation) == 90 && isSwDecoder;
 05475            var swpInW = swapWAndH ? inH : inW;
 05476            var swpInH = swapWAndH ? inW : inH;
 5477
 5478            /* Make main filters for video stream */
 05479            var mainFilters = new List<string>();
 5480
 05481            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5482
 05483            var outFormat = string.Empty;
 05484            if (isSwDecoder)
 5485            {
 5486                // INPUT sw surface(memory)
 5487                // sw deint
 05488                if (doDeintH2645)
 5489                {
 05490                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05491                    mainFilters.Add(swDeintFilter);
 5492                }
 5493
 05494                outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05495                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05496                if (isMjpegEncoder && !doOclTonemap)
 5497                {
 5498                    // sw decoder + hw mjpeg encoder
 05499                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5500                }
 5501
 5502                // sw scale
 05503                mainFilters.Add(swScaleFilter);
 05504                mainFilters.Add("format=" + outFormat);
 5505
 5506                // keep video at memory except ocl tonemap,
 5507                // since the overhead caused by hwupload >>> using sw filter.
 5508                // sw => hw
 05509                if (doOclTonemap)
 5510                {
 05511                    mainFilters.Add("hwupload=derive_device=opencl");
 5512                }
 5513            }
 05514            else if (isVaapiDecoder)
 5515            {
 5516                // INPUT vaapi surface(vram)
 5517                // hw deint
 05518                if (doDeintH2645)
 5519                {
 05520                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05521                    mainFilters.Add(deintFilter);
 5522                }
 5523
 05524                outFormat = doOclTonemap ? string.Empty : "nv12";
 05525                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, inW, inH, reqW, reqH, reqMaxW, 
 5526
 05527                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5528                {
 05529                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05530                    hwScaleFilter += ":mode=hq";
 5531                }
 5532
 5533                // allocate extra pool sizes for vaapi vpp
 05534                if (!string.IsNullOrEmpty(hwScaleFilter))
 5535                {
 05536                    hwScaleFilter += ":extra_hw_frames=24";
 5537                }
 5538
 5539                // hw scale
 05540                mainFilters.Add(hwScaleFilter);
 5541            }
 5542
 05543            if (doOclTonemap && isVaapiDecoder)
 5544            {
 05545                if (isi965Driver)
 5546                {
 5547                    // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05548                    mainFilters.Add("hwmap=derive_device=opencl");
 5549                }
 5550                else
 5551                {
 05552                    mainFilters.Add("hwdownload");
 05553                    mainFilters.Add("format=p010le");
 05554                    mainFilters.Add("hwupload=derive_device=opencl");
 5555                }
 5556            }
 5557
 5558            // ocl tonemap
 05559            if (doOclTonemap)
 5560            {
 05561                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05562                mainFilters.Add(tonemapFilter);
 5563            }
 5564
 05565            if (doOclTonemap && isVaInVaOut)
 5566            {
 05567                if (isi965Driver)
 5568                {
 5569                    // OUTPUT vaapi(nv12) surface(vram)
 5570                    // reverse-mapping via vaapi-opencl interop.
 05571                    mainFilters.Add("hwmap=derive_device=vaapi:reverse=1");
 05572                    mainFilters.Add("format=vaapi");
 5573                }
 5574            }
 5575
 05576            var memoryOutput = false;
 05577            var isUploadForOclTonemap = doOclTonemap && (isSwDecoder || (isVaapiDecoder && !isi965Driver));
 05578            var isHwmapNotUsable = hasGraphicalSubs || isUploadForOclTonemap;
 05579            var isHwmapForSubs = hasSubs && isVaapiDecoder;
 05580            var isHwUnmapForTextSubs = hasTextSubs && isVaInVaOut && !isUploadForOclTonemap;
 05581            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap || isHwmapForSubs)
 5582            {
 05583                memoryOutput = true;
 5584
 5585                // OUTPUT nv12 surface(memory)
 5586                // prefer hwmap to hwdownload on opencl/vaapi.
 05587                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap");
 05588                mainFilters.Add("format=nv12");
 5589            }
 5590
 5591            // OUTPUT nv12 surface(memory)
 05592            if (isSwDecoder && isVaapiEncoder)
 5593            {
 05594                memoryOutput = true;
 5595            }
 5596
 05597            if (memoryOutput)
 5598            {
 5599                // text subtitles
 05600                if (hasTextSubs)
 5601                {
 05602                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05603                    mainFilters.Add(textSubtitlesFilter);
 5604                }
 5605            }
 5606
 05607            if (isHwUnmapForTextSubs)
 5608            {
 05609                mainFilters.Add("hwmap");
 05610                mainFilters.Add("format=vaapi");
 5611            }
 05612            else if (memoryOutput && isVaapiEncoder)
 5613            {
 05614                if (!hasGraphicalSubs)
 5615                {
 05616                    mainFilters.Add("hwupload_vaapi");
 5617                }
 5618            }
 5619
 5620            /* Make sub and overlay filters for subtitle stream */
 05621            var subFilters = new List<string>();
 05622            var overlayFilters = new List<string>();
 05623            if (memoryOutput)
 5624            {
 05625                if (hasGraphicalSubs)
 5626                {
 05627                    var subW = state.SubtitleStream?.Width;
 05628                    var subH = state.SubtitleStream?.Height;
 05629                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05630                    subFilters.Add(subPreProcFilters);
 05631                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5632
 05633                    if (isVaapiEncoder)
 5634                    {
 05635                        overlayFilters.Add("hwupload_vaapi");
 5636                    }
 5637                }
 5638            }
 5639
 05640            return (mainFilters, subFilters, overlayFilters);
 5641        }
 5642
 5643        /// <summary>
 5644        /// Gets the parameter of Apple VideoToolBox filter chain.
 5645        /// </summary>
 5646        /// <param name="state">Encoding state.</param>
 5647        /// <param name="options">Encoding options.</param>
 5648        /// <param name="vidEncoder">Video encoder to use.</param>
 5649        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5650        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFilterChain(
 5651            EncodingJobInfo state,
 5652            EncodingOptions options,
 5653            string vidEncoder)
 5654        {
 05655            if (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 5656            {
 05657                return (null, null, null);
 5658            }
 5659
 5660            // ReSharper disable once InconsistentNaming
 05661            var isMacOS = OperatingSystem.IsMacOS();
 05662            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05663            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05664            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05665            var isVtFullSupported = isMacOS && IsVideoToolboxFullSupported();
 5666
 5667            // legacy videotoolbox pipeline (disable hw filters)
 05668            if (!(isVtEncoder || isVtDecoder)
 05669                || !isVtFullSupported
 05670                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5671            {
 05672                return GetSwVidFilterChain(state, options, vidEncoder);
 5673            }
 5674
 5675            // preferred videotoolbox + metal filters pipeline
 05676            return GetAppleVidFiltersPreferred(state, options, vidDecoder, vidEncoder);
 5677        }
 5678
 5679        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFiltersPrefer
 5680            EncodingJobInfo state,
 5681            EncodingOptions options,
 5682            string vidDecoder,
 5683            string vidEncoder)
 5684        {
 05685            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05686            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05687            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5688
 05689            var inW = state.VideoStream?.Width;
 05690            var inH = state.VideoStream?.Height;
 05691            var reqW = state.BaseRequest.Width;
 05692            var reqH = state.BaseRequest.Height;
 05693            var reqMaxW = state.BaseRequest.MaxWidth;
 05694            var reqMaxH = state.BaseRequest.MaxHeight;
 05695            var threeDFormat = state.MediaSource.Video3DFormat;
 5696
 05697            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05698            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05699            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05700            var doVtTonemap = IsVideoToolboxTonemapAvailable(state, options);
 05701            var doMetalTonemap = !doVtTonemap && IsHwTonemapAvailable(state, options);
 05702            var usingHwSurface = isVtDecoder && (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface);
 5703
 05704            var rotation = state.VideoStream?.Rotation ?? 0;
 05705            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05706            var doVtTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_vt");
 05707            var swapWAndH = Math.Abs(rotation) == 90 && doVtTranspose;
 05708            var swpInW = swapWAndH ? inH : inW;
 05709            var swpInH = swapWAndH ? inW : inH;
 5710
 05711            var scaleFormat = string.Empty;
 5712            // Use P010 for Metal tone mapping, otherwise force an 8bit output.
 05713            if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase))
 5714            {
 05715                if (doMetalTonemap)
 5716                {
 05717                    if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 5718                    {
 05719                        scaleFormat = "p010le";
 5720                    }
 5721                }
 5722                else
 5723                {
 05724                    scaleFormat = "nv12";
 5725                }
 5726            }
 5727
 05728            var hwScaleFilter = GetHwScaleFilter("scale", "vt", scaleFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW,
 5729
 05730            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05731            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05732            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05733            var hasAssSubs = hasSubs
 05734                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05735                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5736
 5737            /* Make main filters for video stream */
 05738            var mainFilters = new List<string>();
 5739
 5740            // hw deint
 05741            if (doDeintH2645)
 5742            {
 05743                var deintFilter = GetHwDeinterlaceFilter(state, options, "videotoolbox");
 05744                mainFilters.Add(deintFilter);
 5745            }
 5746
 5747            // hw transpose
 05748            if (doVtTranspose)
 5749            {
 05750                mainFilters.Add($"transpose_vt=dir={transposeDir}");
 5751            }
 5752
 05753            if (doVtTonemap)
 5754            {
 5755                const string VtTonemapArgs = "color_matrix=bt709:color_primaries=bt709:color_transfer=bt709";
 5756
 5757                // scale_vt can handle scaling & tonemapping in one shot, just like vpp_qsv.
 05758                hwScaleFilter = string.IsNullOrEmpty(hwScaleFilter)
 05759                    ? "scale_vt=" + VtTonemapArgs
 05760                    : hwScaleFilter + ":" + VtTonemapArgs;
 5761            }
 5762
 5763            // hw scale & vt tonemap
 05764            mainFilters.Add(hwScaleFilter);
 5765
 5766            // Metal tonemap
 05767            if (doMetalTonemap)
 5768            {
 05769                var tonemapFilter = GetHwTonemapFilter(options, "videotoolbox", "nv12", isMjpegEncoder);
 05770                mainFilters.Add(tonemapFilter);
 5771            }
 5772
 5773            /* Make sub and overlay filters for subtitle stream */
 05774            var subFilters = new List<string>();
 05775            var overlayFilters = new List<string>();
 5776
 05777            if (hasSubs)
 5778            {
 05779                if (hasGraphicalSubs)
 5780                {
 05781                    var subW = state.SubtitleStream?.Width;
 05782                    var subH = state.SubtitleStream?.Height;
 05783                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05784                    subFilters.Add(subPreProcFilters);
 05785                    subFilters.Add("format=bgra");
 5786                }
 05787                else if (hasTextSubs)
 5788                {
 05789                    var framerate = state.VideoStream?.RealFrameRate;
 05790                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5791
 05792                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05793                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05794                    subFilters.Add(alphaSrcFilter);
 05795                    subFilters.Add("format=bgra");
 05796                    subFilters.Add(subTextSubtitlesFilter);
 5797                }
 5798
 05799                subFilters.Add("hwupload");
 05800                overlayFilters.Add("overlay_videotoolbox=eof_action=pass:repeatlast=0");
 5801            }
 5802
 05803            if (usingHwSurface)
 5804            {
 05805                if (!isVtEncoder)
 5806                {
 05807                    mainFilters.Add("hwdownload");
 05808                    mainFilters.Add("format=nv12");
 5809                }
 5810
 05811                return (mainFilters, subFilters, overlayFilters);
 5812            }
 5813
 5814            // For old jellyfin-ffmpeg that has broken hwsurface, add a hwupload
 05815            var needFiltering = mainFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05816                                subFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05817                                overlayFilters.Any(f => !string.IsNullOrEmpty(f));
 05818            if (needFiltering)
 5819            {
 5820                // INPUT videotoolbox/memory surface(vram/uma)
 5821                // this will pass-through automatically if in/out format matches.
 05822                mainFilters.Insert(0, "hwupload");
 05823                mainFilters.Insert(0, "format=nv12|p010le|videotoolbox_vld");
 5824
 05825                if (!isVtEncoder)
 5826                {
 05827                    mainFilters.Add("hwdownload");
 05828                    mainFilters.Add("format=nv12");
 5829                }
 5830            }
 5831
 05832            return (mainFilters, subFilters, overlayFilters);
 5833        }
 5834
 5835        /// <summary>
 5836        /// Gets the parameter of Rockchip RKMPP/RKRGA filter chain.
 5837        /// </summary>
 5838        /// <param name="state">Encoding state.</param>
 5839        /// <param name="options">Encoding options.</param>
 5840        /// <param name="vidEncoder">Video encoder to use.</param>
 5841        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5842        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFilterChain(
 5843            EncodingJobInfo state,
 5844            EncodingOptions options,
 5845            string vidEncoder)
 5846        {
 05847            if (options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 5848            {
 05849                return (null, null, null);
 5850            }
 5851
 05852            var isLinux = OperatingSystem.IsLinux();
 05853            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05854            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05855            var isSwEncoder = !vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05856            var isRkmppOclSupported = isLinux && IsRkmppFullSupported() && IsOpenclFullSupported();
 5857
 05858            if ((isSwDecoder && isSwEncoder)
 05859                || !isRkmppOclSupported
 05860                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5861            {
 05862                return GetSwVidFilterChain(state, options, vidEncoder);
 5863            }
 5864
 5865            // preferred rkmpp + rkrga + opencl filters pipeline
 05866            if (isRkmppOclSupported)
 5867            {
 05868                return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5869            }
 5870
 05871            return (null, null, null);
 5872        }
 5873
 5874        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFiltersPrefer
 5875            EncodingJobInfo state,
 5876            EncodingOptions options,
 5877            string vidDecoder,
 5878            string vidEncoder)
 5879        {
 05880            var inW = state.VideoStream?.Width;
 05881            var inH = state.VideoStream?.Height;
 05882            var reqW = state.BaseRequest.Width;
 05883            var reqH = state.BaseRequest.Height;
 05884            var reqMaxW = state.BaseRequest.MaxWidth;
 05885            var reqMaxH = state.BaseRequest.MaxHeight;
 05886            var threeDFormat = state.MediaSource.Video3DFormat;
 5887
 05888            var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05889            var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05890            var isSwDecoder = !isRkmppDecoder;
 05891            var isSwEncoder = !isRkmppEncoder;
 05892            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05893            var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder;
 05894            var isEncoderSupportAfbc = isRkmppEncoder
 05895                && (vidEncoder.Contains("h264", StringComparison.OrdinalIgnoreCase)
 05896                    || vidEncoder.Contains("hevc", StringComparison.OrdinalIgnoreCase));
 5897
 05898            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05899            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05900            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05901            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5902
 05903            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05904            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05905            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05906            var hasAssSubs = hasSubs
 05907                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05908                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 05909            var subW = state.SubtitleStream?.Width;
 05910            var subH = state.SubtitleStream?.Height;
 5911
 05912            var rotation = state.VideoStream?.Rotation ?? 0;
 05913            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05914            var doRkVppTranspose = !string.IsNullOrEmpty(transposeDir);
 05915            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isRkmppDecoder && doRkVppTranspose));
 05916            var swpInW = swapWAndH ? inH : inW;
 05917            var swpInH = swapWAndH ? inW : inH;
 5918
 5919            /* Make main filters for video stream */
 05920            var mainFilters = new List<string>();
 5921
 05922            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5923
 05924            if (isSwDecoder)
 5925            {
 5926                // INPUT sw surface(memory)
 5927                // sw deint
 05928                if (doDeintH2645)
 5929                {
 05930                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05931                    mainFilters.Add(swDeintFilter);
 5932                }
 5933
 05934                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 05935                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05936                if (isMjpegEncoder && !doOclTonemap)
 5937                {
 5938                    // sw decoder + hw mjpeg encoder
 05939                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5940                }
 5941
 05942                if (!string.IsNullOrEmpty(swScaleFilter))
 5943                {
 05944                    swScaleFilter += ":flags=fast_bilinear";
 5945                }
 5946
 5947                // sw scale
 05948                mainFilters.Add(swScaleFilter);
 05949                mainFilters.Add($"format={outFormat}");
 5950
 5951                // keep video at memory except ocl tonemap,
 5952                // since the overhead caused by hwupload >>> using sw filter.
 5953                // sw => hw
 05954                if (doOclTonemap)
 5955                {
 05956                    mainFilters.Add("hwupload=derive_device=opencl");
 5957                }
 5958            }
 05959            else if (isRkmppDecoder)
 5960            {
 5961                // INPUT rkmpp/drm surface(gem/dma-heap)
 5962
 05963                var isFullAfbcPipeline = isEncoderSupportAfbc && isDrmInDrmOut && !doOclTonemap;
 05964                var swapOutputWandH = doRkVppTranspose && swapWAndH;
 05965                var outFormat = doOclTonemap ? "p010" : "nv12";
 05966                var hwScaleFilter = GetHwScaleFilter("vpp", "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, r
 05967                var doScaling = !string.IsNullOrEmpty(GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, sw
 5968
 05969                if (!hasSubs
 05970                     || doRkVppTranspose
 05971                     || !isFullAfbcPipeline
 05972                     || doScaling)
 5973                {
 05974                    var isScaleRatioSupported = IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 5975
 5976                    // RGA3 hardware only support (1/8 ~ 8) scaling in each blit operation,
 5977                    // but in Trickplay there's a case: (3840/320 == 12), enable 2pass for it
 05978                    if (doScaling && !isScaleRatioSupported)
 5979                    {
 5980                        // Vendor provided BSP kernel has an RGA driver bug that causes the output to be corrupted for P
 5981                        // Use NV15 instead of P010 to avoid the issue.
 5982                        // SDR inputs are using BGRA formats already which is not affected.
 05983                        var intermediateFormat = doOclTonemap ? "nv15" : (isMjpegEncoder ? "bgra" : outFormat);
 05984                        var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={intermediateFormat}:force_o
 05985                        mainFilters.Add(hwScaleFilterFirstPass);
 5986                    }
 5987
 5988                    // The RKMPP MJPEG encoder on some newer chip models no longer supports RGB input.
 5989                    // Use 2pass here to enable RGA output of full-range YUV in the 2nd pass.
 05990                    if (isMjpegEncoder && !doOclTonemap && ((doScaling && isScaleRatioSupported) || !doScaling))
 5991                    {
 05992                        var hwScaleFilterFirstPass = "vpp_rkrga=format=bgra:afbc=1";
 05993                        mainFilters.Add(hwScaleFilterFirstPass);
 5994                    }
 5995
 05996                    if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose)
 5997                    {
 05998                        hwScaleFilter += $":transpose={transposeDir}";
 5999                    }
 6000
 6001                    // try enabling AFBC to save DDR bandwidth
 06002                    if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline)
 6003                    {
 06004                        hwScaleFilter += ":afbc=1";
 6005                    }
 6006
 6007                    // hw transpose & scale
 06008                    mainFilters.Add(hwScaleFilter);
 6009                }
 6010            }
 6011
 06012            if (doOclTonemap && isRkmppDecoder)
 6013            {
 6014                // map from rkmpp/drm to opencl via drm-opencl interop.
 06015                mainFilters.Add("hwmap=derive_device=opencl");
 6016            }
 6017
 6018            // ocl tonemap
 06019            if (doOclTonemap)
 6020            {
 06021                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 06022                mainFilters.Add(tonemapFilter);
 6023            }
 6024
 06025            var memoryOutput = false;
 06026            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 06027            if ((isRkmppDecoder && isSwEncoder) || isUploadForOclTonemap)
 6028            {
 06029                memoryOutput = true;
 6030
 6031                // OUTPUT nv12 surface(memory)
 06032                mainFilters.Add("hwdownload");
 06033                mainFilters.Add("format=nv12");
 6034            }
 6035
 6036            // OUTPUT nv12 surface(memory)
 06037            if (isSwDecoder && isRkmppEncoder)
 6038            {
 06039                memoryOutput = true;
 6040            }
 6041
 06042            if (memoryOutput)
 6043            {
 6044                // text subtitles
 06045                if (hasTextSubs)
 6046                {
 06047                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 06048                    mainFilters.Add(textSubtitlesFilter);
 6049                }
 6050            }
 6051
 06052            if (isDrmInDrmOut)
 6053            {
 06054                if (doOclTonemap)
 6055                {
 6056                    // OUTPUT drm(nv12) surface(gem/dma-heap)
 6057                    // reverse-mapping via drm-opencl interop.
 06058                    mainFilters.Add("hwmap=derive_device=rkmpp:reverse=1");
 06059                    mainFilters.Add("format=drm_prime");
 6060                }
 6061            }
 6062
 6063            /* Make sub and overlay filters for subtitle stream */
 06064            var subFilters = new List<string>();
 06065            var overlayFilters = new List<string>();
 06066            if (isDrmInDrmOut)
 6067            {
 06068                if (hasSubs)
 6069                {
 06070                    var subMaxH = 1080;
 06071                    if (hasGraphicalSubs)
 6072                    {
 06073                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 06074                        subFilters.Add(subPreProcFilters);
 06075                        subFilters.Add("format=bgra");
 6076                    }
 06077                    else if (hasTextSubs)
 6078                    {
 06079                        var framerate = state.VideoStream?.RealFrameRate;
 06080                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 6081
 6082                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 06083                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, subMaxH, subF
 06084                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 06085                        subFilters.Add(alphaSrcFilter);
 06086                        subFilters.Add("format=bgra");
 06087                        subFilters.Add(subTextSubtitlesFilter);
 6088                    }
 6089
 06090                    subFilters.Add("hwupload=derive_device=rkmpp");
 6091
 6092                    // offload 1080p+ subtitles swscale upscaling from CPU to RGA
 06093                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 06094                    if (overlayW.HasValue && overlayH.HasValue && overlayH.Value > subMaxH)
 6095                    {
 06096                        subFilters.Add($"vpp_rkrga=w={overlayW.Value}:h={overlayH.Value}:format=bgra:afbc=1");
 6097                    }
 6098
 6099                    // try enabling AFBC to save DDR bandwidth
 06100                    var hwOverlayFilter = "overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12";
 06101                    if (isEncoderSupportAfbc)
 6102                    {
 06103                        hwOverlayFilter += ":afbc=1";
 6104                    }
 6105
 06106                    overlayFilters.Add(hwOverlayFilter);
 6107                }
 6108            }
 06109            else if (memoryOutput)
 6110            {
 06111                if (hasGraphicalSubs)
 6112                {
 06113                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 06114                    subFilters.Add(subPreProcFilters);
 06115                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 6116                }
 6117            }
 6118
 06119            return (mainFilters, subFilters, overlayFilters);
 6120        }
 6121
 6122        /// <summary>
 6123        /// Gets the parameter of video processing filters.
 6124        /// </summary>
 6125        /// <param name="state">Encoding state.</param>
 6126        /// <param name="options">Encoding options.</param>
 6127        /// <param name="outputVideoCodec">Video codec to use.</param>
 6128        /// <returns>The video processing filters parameter.</returns>
 6129        public string GetVideoProcessingFilterParam(
 6130            EncodingJobInfo state,
 6131            EncodingOptions options,
 6132            string outputVideoCodec)
 6133        {
 06134            var videoStream = state.VideoStream;
 06135            if (videoStream is null)
 6136            {
 06137                return string.Empty;
 6138            }
 6139
 06140            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 06141            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 06142            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 6143
 6144            List<string> mainFilters;
 6145            List<string> subFilters;
 6146            List<string> overlayFilters;
 6147
 06148            (mainFilters, subFilters, overlayFilters) = options.HardwareAccelerationType switch
 06149            {
 06150                HardwareAccelerationType.vaapi => GetVaapiVidFilterChain(state, options, outputVideoCodec),
 06151                HardwareAccelerationType.amf => GetAmdVidFilterChain(state, options, outputVideoCodec),
 06152                HardwareAccelerationType.qsv => GetIntelVidFilterChain(state, options, outputVideoCodec),
 06153                HardwareAccelerationType.nvenc => GetNvidiaVidFilterChain(state, options, outputVideoCodec),
 06154                HardwareAccelerationType.videotoolbox => GetAppleVidFilterChain(state, options, outputVideoCodec),
 06155                HardwareAccelerationType.rkmpp => GetRkmppVidFilterChain(state, options, outputVideoCodec),
 06156                _ => GetSwVidFilterChain(state, options, outputVideoCodec),
 06157            };
 6158
 06159            mainFilters?.RemoveAll(string.IsNullOrEmpty);
 06160            subFilters?.RemoveAll(string.IsNullOrEmpty);
 06161            overlayFilters?.RemoveAll(string.IsNullOrEmpty);
 6162
 06163            var framerate = GetFramerateParam(state);
 06164            if (framerate.HasValue)
 6165            {
 06166                mainFilters.Insert(0, string.Format(
 06167                    CultureInfo.InvariantCulture,
 06168                    "fps={0}",
 06169                    framerate.Value));
 6170            }
 6171
 06172            var mainStr = string.Empty;
 06173            if (mainFilters?.Count > 0)
 6174            {
 06175                mainStr = string.Format(
 06176                    CultureInfo.InvariantCulture,
 06177                    "{0}",
 06178                    string.Join(',', mainFilters));
 6179            }
 6180
 06181            if (overlayFilters?.Count == 0)
 6182            {
 6183                // -vf "scale..."
 06184                return string.IsNullOrEmpty(mainStr) ? string.Empty : " -vf \"" + mainStr + "\"";
 6185            }
 6186
 06187            if (overlayFilters?.Count > 0
 06188                && subFilters?.Count > 0
 06189                && state.SubtitleStream is not null)
 6190            {
 6191                // overlay graphical/text subtitles
 06192                var subStr = string.Format(
 06193                        CultureInfo.InvariantCulture,
 06194                        "{0}",
 06195                        string.Join(',', subFilters));
 6196
 06197                var overlayStr = string.Format(
 06198                        CultureInfo.InvariantCulture,
 06199                        "{0}",
 06200                        string.Join(',', overlayFilters));
 6201
 06202                var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
 06203                var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 06204                var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 6205
 06206                if (hasSubs)
 6207                {
 6208                    // -filter_complex "[0:s]scale=s[sub]..."
 06209                    var filterStr = string.IsNullOrEmpty(mainStr)
 06210                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]{5}\""
 06211                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6212
 06213                    if (hasTextSubs)
 6214                    {
 06215                        filterStr = string.IsNullOrEmpty(mainStr)
 06216                            ? " -filter_complex \"{4}[sub];[0:{2}][sub]{5}\""
 06217                            : " -filter_complex \"{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6218                    }
 6219
 06220                    return string.Format(
 06221                        CultureInfo.InvariantCulture,
 06222                        filterStr,
 06223                        mapPrefix,
 06224                        subtitleStreamIndex,
 06225                        videoStreamIndex,
 06226                        mainStr,
 06227                        subStr,
 06228                        overlayStr);
 6229                }
 6230            }
 6231
 06232            return string.Empty;
 6233        }
 6234
 6235        public string GetOverwriteColorPropertiesParam(EncodingJobInfo state, bool isTonemapAvailable)
 6236        {
 06237            if (isTonemapAvailable)
 6238            {
 06239                return GetInputHdrParam(state.VideoStream?.ColorTransfer);
 6240            }
 6241
 06242            return GetOutputSdrParam(null);
 6243        }
 6244
 6245        public string GetInputHdrParam(string colorTransfer)
 6246        {
 06247            if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
 6248            {
 6249                // HLG
 06250                return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
 6251            }
 6252
 6253            // HDR10
 06254            return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc";
 6255        }
 6256
 6257        public string GetOutputSdrParam(string tonemappingRange)
 6258        {
 6259            // SDR
 06260            if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
 6261            {
 06262                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
 6263            }
 6264
 06265            if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
 6266            {
 06267                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc";
 6268            }
 6269
 06270            return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
 6271        }
 6272
 6273        public static int GetVideoColorBitDepth(EncodingJobInfo state)
 6274        {
 06275            var videoStream = state.VideoStream;
 06276            if (videoStream is not null)
 6277            {
 06278                if (videoStream.BitDepth.HasValue)
 6279                {
 06280                    return videoStream.BitDepth.Value;
 6281                }
 6282
 06283                if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
 06284                    || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
 06285                    || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 06286                    || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
 6287                {
 06288                    return 8;
 6289                }
 6290
 06291                if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 06292                    || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 06293                    || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
 6294                {
 06295                    return 10;
 6296                }
 6297
 06298                if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 06299                    || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 06300                    || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
 6301                {
 06302                    return 12;
 6303                }
 6304
 06305                return 8;
 6306            }
 6307
 06308            return 0;
 6309        }
 6310
 6311        /// <summary>
 6312        /// Gets the ffmpeg option string for the hardware accelerated video decoder.
 6313        /// </summary>
 6314        /// <param name="state">The encoding job info.</param>
 6315        /// <param name="options">The encoding options.</param>
 6316        /// <returns>The option string or null if none available.</returns>
 6317        protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
 6318        {
 06319            var videoStream = state.VideoStream;
 06320            var mediaSource = state.MediaSource;
 06321            if (videoStream is null || mediaSource is null)
 6322            {
 06323                return null;
 6324            }
 6325
 6326            // HWA decoders can handle both video files and video folders.
 06327            var videoType = state.VideoType;
 06328            if (videoType != VideoType.VideoFile
 06329                && videoType != VideoType.Iso
 06330                && videoType != VideoType.Dvd
 06331                && videoType != VideoType.BluRay)
 6332            {
 06333                return null;
 6334            }
 6335
 06336            if (IsCopyCodec(state.OutputVideoCodec))
 6337            {
 06338                return null;
 6339            }
 6340
 06341            var hardwareAccelerationType = options.HardwareAccelerationType;
 6342
 06343            if (!string.IsNullOrEmpty(videoStream.Codec) && hardwareAccelerationType != HardwareAccelerationType.none)
 6344            {
 06345                var bitDepth = GetVideoColorBitDepth(state);
 6346
 6347                // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support for most platforms
 06348                if (bitDepth == 10
 06349                    && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06350                         || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
 06351                         || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)
 06352                         || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)))
 6353                {
 6354                    // RKMPP has H.264 Hi10P decoder
 06355                    bool hasHardwareHi10P = hardwareAccelerationType == HardwareAccelerationType.rkmpp;
 6356
 6357                    // VideoToolbox on Apple Silicon has H.264 Hi10P mode enabled after macOS 14.6
 06358                    if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox)
 6359                    {
 06360                        var ver = Environment.OSVersion.Version;
 06361                        var arch = RuntimeInformation.OSArchitecture;
 06362                        if (arch.Equals(Architecture.Arm64) && ver >= new Version(14, 6))
 6363                        {
 06364                            hasHardwareHi10P = true;
 6365                        }
 6366                    }
 6367
 06368                    if (!hasHardwareHi10P
 06369                        && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6370                    {
 06371                        return null;
 6372                    }
 6373                }
 6374
 6375                // Block unsupported H.264 Hi422P and Hi444PP profiles, which can be encoded with 4:2:0 pixel format
 06376                if (string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6377                {
 06378                    if (videoStream.Profile.Contains("4:2:2", StringComparison.OrdinalIgnoreCase)
 06379                        || videoStream.Profile.Contains("4:4:4", StringComparison.OrdinalIgnoreCase))
 6380                    {
 6381                        // VideoToolbox on Apple Silicon has H.264 Hi444PP and theoretically also has Hi422P
 06382                        if (!(hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06383                              && RuntimeInformation.OSArchitecture.Equals(Architecture.Arm64)))
 6384                        {
 06385                            return null;
 6386                        }
 6387                    }
 6388                }
 6389
 06390                var decoder = hardwareAccelerationType switch
 06391                {
 06392                    HardwareAccelerationType.vaapi => GetVaapiVidDecoder(state, options, videoStream, bitDepth),
 06393                    HardwareAccelerationType.amf => GetAmfVidDecoder(state, options, videoStream, bitDepth),
 06394                    HardwareAccelerationType.qsv => GetQsvHwVidDecoder(state, options, videoStream, bitDepth),
 06395                    HardwareAccelerationType.nvenc => GetNvdecVidDecoder(state, options, videoStream, bitDepth),
 06396                    HardwareAccelerationType.videotoolbox => GetVideotoolboxVidDecoder(state, options, videoStream, bitD
 06397                    HardwareAccelerationType.rkmpp => GetRkmppVidDecoder(state, options, videoStream, bitDepth),
 06398                    _ => string.Empty
 06399                };
 6400
 06401                if (!string.IsNullOrEmpty(decoder))
 6402                {
 06403                    return decoder;
 6404                }
 6405            }
 6406
 6407            // leave blank so ffmpeg will decide
 06408            return null;
 6409        }
 6410
 6411        /// <summary>
 6412        /// Gets a hw decoder name.
 6413        /// </summary>
 6414        /// <param name="options">Encoding options.</param>
 6415        /// <param name="decoderPrefix">Decoder prefix.</param>
 6416        /// <param name="decoderSuffix">Decoder suffix.</param>
 6417        /// <param name="videoCodec">Video codec to use.</param>
 6418        /// <param name="bitDepth">Video color bit depth.</param>
 6419        /// <returns>Hardware decoder name.</returns>
 6420        public string GetHwDecoderName(EncodingOptions options, string decoderPrefix, string decoderSuffix, string video
 6421        {
 06422            if (string.IsNullOrEmpty(decoderPrefix) || string.IsNullOrEmpty(decoderSuffix))
 6423            {
 06424                return null;
 6425            }
 6426
 06427            var decoderName = decoderPrefix + '_' + decoderSuffix;
 6428
 06429            var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains
 6430
 6431            // VideoToolbox decoders have built-in SW fallback
 06432            if (bitDepth == 10
 06433                && isCodecAvailable
 06434                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6435            {
 06436                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06437                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
 06438                    && !options.EnableDecodingColorDepth10Hevc)
 6439                {
 06440                    return null;
 6441                }
 6442
 06443                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06444                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06445                    && !options.EnableDecodingColorDepth10Vp9)
 6446                {
 06447                    return null;
 6448                }
 6449            }
 6450
 06451            if (string.Equals(decoderSuffix, "cuvid", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdec
 6452            {
 06453                return null;
 6454            }
 6455
 06456            if (string.Equals(decoderSuffix, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwD
 6457            {
 06458                return null;
 6459            }
 6460
 06461            if (string.Equals(decoderSuffix, "rkmpp", StringComparison.OrdinalIgnoreCase))
 6462            {
 06463                return null;
 6464            }
 6465
 06466            return isCodecAvailable ? (" -c:v " + decoderName) : null;
 6467        }
 6468
 6469        /// <summary>
 6470        /// Gets a hwaccel type to use as a hardware decoder depending on the system.
 6471        /// </summary>
 6472        /// <param name="state">Encoding state.</param>
 6473        /// <param name="options">Encoding options.</param>
 6474        /// <param name="videoCodec">Video codec to use.</param>
 6475        /// <param name="bitDepth">Video color bit depth.</param>
 6476        /// <param name="outputHwSurface">Specifies if output hw surface.</param>
 6477        /// <returns>Hardware accelerator type.</returns>
 6478        public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, int bitDepth, bo
 6479        {
 06480            var isWindows = OperatingSystem.IsWindows();
 06481            var isLinux = OperatingSystem.IsLinux();
 06482            var isMacOS = OperatingSystem.IsMacOS();
 06483            var isD3d11Supported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va");
 06484            var isVaapiSupported = isLinux && IsVaapiSupported(state);
 06485            var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported();
 06486            var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv");
 06487            var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
 06488            var isRkmppSupported = isLinux && IsRkmppFullSupported();
 06489            var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCas
 06490            var hardwareAccelerationType = options.HardwareAccelerationType;
 6491
 06492            var ffmpegVersion = _mediaEncoder.EncoderVersion;
 6493
 6494            // Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
 06495            var isAv1 = ffmpegVersion < _minFFmpegImplicitHwaccel
 06496                && string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
 6497
 6498            // Allow profile mismatch if decoding H.264 baseline with d3d11va and vaapi hwaccels.
 06499            var profileMismatch = string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
 06500                && string.Equals(state.VideoStream?.Profile, "baseline", StringComparison.OrdinalIgnoreCase);
 6501
 6502            // Disable the extra internal copy in nvdec. We already handle it in filter chain.
 06503            var nvdecNoInternalCopy = ffmpegVersion >= _minFFmpegHwaUnsafeOutput;
 6504
 6505            // Strip the display rotation side data from the transposed fmp4 output stream.
 06506            var stripRotationData = (state.VideoStream?.Rotation ?? 0) != 0
 06507                && ffmpegVersion >= _minFFmpegDisplayRotationOption;
 06508            var stripRotationDataArgs = stripRotationData ? " -display_rotation 0" : string.Empty;
 6509
 6510            // VideoToolbox decoders have built-in SW fallback
 06511            if (isCodecAvailable
 06512                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6513            {
 06514                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06515                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase))
 6516                {
 06517                    if (IsVideoStreamHevcRext(state))
 6518                    {
 06519                        if (bitDepth <= 10 && !options.EnableDecodingColorDepth10HevcRext)
 6520                        {
 06521                            return null;
 6522                        }
 6523
 06524                        if (bitDepth == 12 && !options.EnableDecodingColorDepth12HevcRext)
 6525                        {
 06526                            return null;
 6527                        }
 6528
 06529                        if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06530                            && !_mediaEncoder.IsVaapiDeviceInteliHD)
 6531                        {
 06532                            return null;
 6533                        }
 6534                    }
 06535                    else if (bitDepth == 10 && !options.EnableDecodingColorDepth10Hevc)
 6536                    {
 06537                        return null;
 6538                    }
 6539                }
 6540
 06541                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06542                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06543                    && bitDepth == 10
 06544                    && !options.EnableDecodingColorDepth10Vp9)
 6545                {
 06546                    return null;
 6547                }
 6548            }
 6549
 6550            // Intel qsv/d3d11va/vaapi
 06551            if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 6552            {
 06553                if (options.PreferSystemNativeHwDecoder)
 6554                {
 06555                    if (isVaapiSupported && isCodecAvailable)
 6556                    {
 06557                        return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + st
 06558                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " 
 6559                    }
 6560
 06561                    if (isD3d11Supported && isCodecAvailable)
 6562                    {
 06563                        return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + 
 06564                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 
 6565                    }
 6566                }
 6567                else
 6568                {
 06569                    if (isQsvSupported && isCodecAvailable)
 6570                    {
 06571                        return " -hwaccel qsv" + (outputHwSurface ? " -hwaccel_output_format qsv -noautorotate" + stripR
 6572                    }
 6573                }
 6574            }
 6575
 6576            // Nvidia cuda
 06577            if (hardwareAccelerationType == HardwareAccelerationType.nvenc)
 6578            {
 06579                if (isCudaSupported && isCodecAvailable)
 6580                {
 06581                    if (options.EnableEnhancedNvdecDecoder)
 6582                    {
 6583                        // set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support.
 06584                        return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stri
 06585                            + (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + 
 6586                    }
 6587
 6588                    // cuvid decoder doesn't have threading issue.
 06589                    return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stripRot
 6590                }
 6591            }
 6592
 6593            // Amd d3d11va
 06594            if (hardwareAccelerationType == HardwareAccelerationType.amf)
 6595            {
 06596                if (isD3d11Supported && isCodecAvailable)
 6597                {
 06598                    return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + stri
 06599                        + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 2" +
 6600                }
 6601            }
 6602
 6603            // Vaapi
 06604            if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06605                && isVaapiSupported
 06606                && isCodecAvailable)
 6607            {
 06608                return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + stripRotat
 06609                    + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1
 6610            }
 6611
 6612            // Apple videotoolbox
 06613            if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06614                && isVideotoolboxSupported
 06615                && isCodecAvailable)
 6616            {
 06617                return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string
 6618            }
 6619
 6620            // Rockchip rkmpp
 06621            if (hardwareAccelerationType == HardwareAccelerationType.rkmpp
 06622                && isRkmppSupported
 06623                && isCodecAvailable)
 6624            {
 06625                return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime -noautorotate" + stripR
 6626            }
 6627
 06628            return null;
 6629        }
 6630
 6631        public string GetQsvHwVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6632        {
 06633            var isWindows = OperatingSystem.IsWindows();
 06634            var isLinux = OperatingSystem.IsLinux();
 6635
 06636            if ((!isWindows && !isLinux)
 06637                || options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 6638            {
 06639                return null;
 6640            }
 6641
 06642            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 06643            var isIntelDx11OclSupported = isWindows
 06644                && _mediaEncoder.SupportsHwaccel("d3d11va")
 06645                && isQsvOclSupported;
 06646            var isIntelVaapiOclSupported = isLinux
 06647                && IsVaapiSupported(state)
 06648                && isQsvOclSupported;
 06649            var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
 06650                && _mediaEncoder.SupportsFilter("alphasrc");
 6651
 06652            var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06653                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06654            var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 06655            var is8_10_12bitSwFormatsQsv = is8_10bitSwFormatsQsv
 06656                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06657                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06658                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06659                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06660                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06661                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06662                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6663            // TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
 6664
 06665            if (is8bitSwFormatsQsv)
 6666            {
 06667                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06668                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6669                {
 06670                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6671                }
 6672
 06673                if (string.Equals(videoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase))
 6674                {
 06675                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6676                }
 6677
 06678                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6679                {
 06680                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6681                }
 6682
 06683                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6684                {
 06685                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6686                }
 6687            }
 6688
 06689            if (is8_10bitSwFormatsQsv)
 6690            {
 06691                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6692                {
 06693                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6694                }
 6695
 06696                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 6697                {
 06698                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6699                }
 6700            }
 6701
 06702            if (is8_10_12bitSwFormatsQsv)
 6703            {
 06704                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06705                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6706                {
 06707                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6708                }
 6709            }
 6710
 06711            return null;
 6712        }
 6713
 6714        public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6715        {
 06716            if ((!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
 06717                || options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 6718            {
 06719                return null;
 6720            }
 6721
 06722            var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
 06723            var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06724                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06725            var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06726            var is8_10_12bitSwFormatsNvdec = is8_10bitSwFormatsNvdec
 06727                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06728                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06729                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06730                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6731            // TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
 6732
 06733            if (is8bitSwFormatsNvdec)
 6734            {
 06735                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06736                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6737                {
 06738                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6739                }
 6740
 06741                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6742                {
 06743                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6744                }
 6745
 06746                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6747                {
 06748                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6749                }
 6750
 06751                if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6752                {
 06753                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpe
 6754                }
 6755
 06756                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6757                {
 06758                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6759                }
 6760            }
 6761
 06762            if (is8_10bitSwFormatsNvdec)
 6763            {
 06764                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6765                {
 06766                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6767                }
 6768
 06769                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6770                {
 06771                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6772                }
 6773            }
 6774
 06775            if (is8_10_12bitSwFormatsNvdec)
 6776            {
 06777                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06778                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6779                {
 06780                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6781                }
 6782            }
 6783
 06784            return null;
 6785        }
 6786
 6787        public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitD
 6788        {
 06789            if (!OperatingSystem.IsWindows()
 06790                || options.HardwareAccelerationType != HardwareAccelerationType.amf)
 6791            {
 06792                return null;
 6793            }
 6794
 06795            var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
 06796                && IsOpenclFullSupported()
 06797                && _mediaEncoder.SupportsFilter("alphasrc");
 06798            var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06799                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06800            var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 6801
 06802            if (is8bitSwFormatsAmf)
 6803            {
 06804                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06805                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6806                {
 06807                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6808                }
 6809
 06810                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6811                {
 06812                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6813                }
 6814
 06815                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6816                {
 06817                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6818                }
 6819            }
 6820
 06821            if (is8_10bitSwFormatsAmf)
 6822            {
 06823                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06824                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6825                {
 06826                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6827                }
 6828
 06829                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6830                {
 06831                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6832                }
 6833
 06834                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6835                {
 06836                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6837                }
 6838            }
 6839
 06840            return null;
 6841        }
 6842
 6843        public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6844        {
 06845            if (!OperatingSystem.IsLinux()
 06846                || options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 6847            {
 06848                return null;
 6849            }
 6850
 06851            var hwSurface = IsVaapiSupported(state)
 06852                && IsVaapiFullSupported()
 06853                && IsOpenclFullSupported()
 06854                && _mediaEncoder.SupportsFilter("alphasrc");
 06855            var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06856                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06857            var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06858            var is8_10_12bitSwFormatsVaapi = is8_10bitSwFormatsVaapi
 06859                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06860                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06861                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06862                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06863                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06864                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06865                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6866
 06867            if (is8bitSwFormatsVaapi)
 6868            {
 06869                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06870                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6871                {
 06872                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6873                }
 6874
 06875                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6876                {
 06877                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6878                }
 6879
 06880                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6881                {
 06882                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6883                }
 6884
 06885                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6886                {
 06887                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6888                }
 6889            }
 6890
 06891            if (is8_10bitSwFormatsVaapi)
 6892            {
 06893                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6894                {
 06895                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6896                }
 6897
 06898                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6899                {
 06900                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6901                }
 6902            }
 6903
 06904            if (is8_10_12bitSwFormatsVaapi)
 6905            {
 06906                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06907                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6908                {
 06909                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6910                }
 6911            }
 6912
 06913            return null;
 6914        }
 6915
 6916        public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream,
 6917        {
 06918            if (!OperatingSystem.IsMacOS()
 06919                || options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 6920            {
 06921                return null;
 6922            }
 6923
 06924            var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase
 06925                                    || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnore
 06926            var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, String
 06927            var is8_10_12bitSwFormatsVt = is8_10bitSwFormatsVt
 06928                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06929                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06930                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06931                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06932                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06933                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06934                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 06935            var isAv1SupportedSwFormatsVt = is8_10bitSwFormatsVt || string.Equals("yuv420p12le", videoStream.PixelFormat
 6936
 6937            // The related patches make videotoolbox hardware surface working is only available in jellyfin-ffmpeg 7.0.1
 06938            bool useHwSurface = (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface) && IsVideoToolboxFullSupp
 6939
 06940            if (is8bitSwFormatsVt)
 6941            {
 06942                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6943                {
 06944                    return GetHwaccelType(state, options, "vp8", bitDepth, useHwSurface);
 6945                }
 6946            }
 6947
 06948            if (is8_10bitSwFormatsVt)
 6949            {
 06950                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06951                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6952                {
 06953                    return GetHwaccelType(state, options, "h264", bitDepth, useHwSurface);
 6954                }
 6955
 06956                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6957                {
 06958                    return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
 6959                }
 6960            }
 6961
 06962            if (is8_10_12bitSwFormatsVt)
 6963            {
 06964                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06965                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6966                {
 06967                    return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
 6968                }
 6969
 06970                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06971                    && isAv1SupportedSwFormatsVt
 06972                    && _mediaEncoder.IsVideoToolboxAv1DecodeAvailable)
 6973                {
 06974                    return GetHwaccelType(state, options, "av1", bitDepth, useHwSurface);
 6975                }
 6976            }
 6977
 06978            return null;
 6979        }
 6980
 6981        public string GetRkmppVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6982        {
 06983            var isLinux = OperatingSystem.IsLinux();
 6984
 06985            if (!isLinux
 06986                || options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 6987            {
 06988                return null;
 6989            }
 6990
 06991            var inW = state.VideoStream?.Width;
 06992            var inH = state.VideoStream?.Height;
 06993            var reqW = state.BaseRequest.Width;
 06994            var reqH = state.BaseRequest.Height;
 06995            var reqMaxW = state.BaseRequest.MaxWidth;
 06996            var reqMaxH = state.BaseRequest.MaxHeight;
 6997
 6998            // rkrga RGA2e supports range from 1/16 to 16
 06999            if (!IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 16.0f))
 7000            {
 07001                return null;
 7002            }
 7003
 07004            var isRkmppOclSupported = IsRkmppFullSupported() && IsOpenclFullSupported();
 07005            var hwSurface = isRkmppOclSupported
 07006                && _mediaEncoder.SupportsFilter("alphasrc");
 7007
 7008            // rkrga RGA3 supports range from 1/8 to 8
 07009            var isAfbcSupported = hwSurface && IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 7010
 7011            // TODO: add more 8/10bit and 4:2:2 formats for Rkmpp after finishing the ffcheck tool
 07012            var is8bitSwFormatsRkmpp = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 07013                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 07014            var is10bitSwFormatsRkmpp = string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIg
 07015            var is8_10bitSwFormatsRkmpp = is8bitSwFormatsRkmpp || is10bitSwFormatsRkmpp;
 7016
 7017            // nv15 and nv20 are bit-stream only formats
 07018            if (is10bitSwFormatsRkmpp && !hwSurface)
 7019            {
 07020                return null;
 7021            }
 7022
 07023            if (is8bitSwFormatsRkmpp)
 7024            {
 07025                if (string.Equals(videoStream.Codec, "mpeg1video", StringComparison.OrdinalIgnoreCase))
 7026                {
 07027                    return GetHwaccelType(state, options, "mpeg1video", bitDepth, hwSurface);
 7028                }
 7029
 07030                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 7031                {
 07032                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 7033                }
 7034
 07035                if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 7036                {
 07037                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
 7038                }
 7039
 07040                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 7041                {
 07042                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 7043                }
 7044            }
 7045
 07046            if (is8_10bitSwFormatsRkmpp)
 7047            {
 07048                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 07049                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 7050                {
 07051                    var accelType = GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 07052                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7053                }
 7054
 07055                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 07056                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 7057                {
 07058                    var accelType = GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 07059                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7060                }
 7061
 07062                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 7063                {
 07064                    var accelType = GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 07065                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7066                }
 7067
 07068                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 7069                {
 7070                    // there's an issue about AV1 AFBC on RK3588, disable it for now until it's fixed upstream
 07071                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 7072                }
 7073            }
 7074
 07075            return null;
 7076        }
 7077
 7078        /// <summary>
 7079        /// Gets the number of threads.
 7080        /// </summary>
 7081        /// <param name="state">Encoding state.</param>
 7082        /// <param name="encodingOptions">Encoding options.</param>
 7083        /// <param name="outputVideoCodec">Video codec to use.</param>
 7084        /// <returns>Number of threads.</returns>
 7085#nullable enable
 7086        public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVide
 7087        {
 07088            var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount;
 7089
 07090            if (threads <= 0)
 7091            {
 7092                // Automatically set thread count
 07093                return 0;
 7094            }
 7095
 07096            return Math.Min(threads, Environment.ProcessorCount);
 7097        }
 7098
 7099#nullable disable
 7100        public void TryStreamCopy(EncodingJobInfo state, EncodingOptions options)
 7101        {
 07102            if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream))
 7103            {
 07104                state.OutputVideoCodec = "copy";
 7105            }
 7106            else
 7107            {
 07108                var user = state.User;
 7109
 7110                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07111                if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
 7112                {
 07113                    state.OutputVideoCodec = "copy";
 7114                }
 7115            }
 7116
 07117            var preventHlsAudioCopy = state.TranscodingType is TranscodingJobType.Hls
 07118                && state.VideoStream is not null
 07119                && !IsCopyCodec(state.OutputVideoCodec)
 07120                && options.HlsAudioSeekStrategy is HlsAudioSeekStrategy.TranscodeAudio;
 7121
 07122            if (state.AudioStream is not null
 07123                && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs)
 07124                && !preventHlsAudioCopy)
 7125            {
 07126                state.OutputAudioCodec = "copy";
 7127            }
 7128            else
 7129            {
 07130                var user = state.User;
 7131
 7132                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07133                if (user is not null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
 7134                {
 07135                    state.OutputAudioCodec = "copy";
 7136                }
 7137            }
 07138        }
 7139
 7140        private string GetFfmpegAnalyzeDurationArg(EncodingJobInfo state)
 7141        {
 07142            var analyzeDurationArgument = string.Empty;
 7143
 7144            // Apply -analyzeduration as per the environment variable,
 7145            // otherwise ffmpeg will break on certain files due to default value is 0.
 07146            var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
 7147
 07148            if (state.MediaSource.AnalyzeDurationMs > 0)
 7149            {
 07150                analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToStr
 7151            }
 07152            else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
 7153            {
 07154                analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
 7155            }
 7156
 07157            return analyzeDurationArgument;
 7158        }
 7159
 7160        private string GetFfmpegProbesizeArg()
 7161        {
 07162            var ffmpegProbeSize = _config.GetFFmpegProbeSize();
 7163
 07164            if (!string.IsNullOrEmpty(ffmpegProbeSize))
 7165            {
 07166                return $"-probesize {ffmpegProbeSize}";
 7167            }
 7168
 07169            return string.Empty;
 7170        }
 7171
 7172        public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
 7173        {
 07174            var inputModifier = string.Empty;
 07175            var analyzeDurationArgument = GetFfmpegAnalyzeDurationArg(state);
 7176
 07177            if (!string.IsNullOrEmpty(analyzeDurationArgument))
 7178            {
 07179                inputModifier += " " + analyzeDurationArgument;
 7180            }
 7181
 07182            inputModifier = inputModifier.Trim();
 7183
 7184            // Apply -probesize if configured
 07185            var ffmpegProbeSizeArgument = GetFfmpegProbesizeArg();
 7186
 07187            if (!string.IsNullOrEmpty(ffmpegProbeSizeArgument))
 7188            {
 07189                inputModifier += " " + ffmpegProbeSizeArgument;
 7190            }
 7191
 07192            var userAgentParam = GetUserAgentParam(state);
 7193
 07194            if (!string.IsNullOrEmpty(userAgentParam))
 7195            {
 07196                inputModifier += " " + userAgentParam;
 7197            }
 7198
 07199            inputModifier = inputModifier.Trim();
 7200
 07201            var refererParam = GetRefererParam(state);
 7202
 07203            if (!string.IsNullOrEmpty(refererParam))
 7204            {
 07205                inputModifier += " " + refererParam;
 7206            }
 7207
 07208            inputModifier = inputModifier.Trim();
 7209
 07210            inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions, segmentContainer);
 07211            inputModifier = inputModifier.Trim();
 7212
 07213            if (state.InputProtocol == MediaProtocol.Rtsp)
 7214            {
 07215                inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
 7216            }
 7217
 07218            if (!string.IsNullOrEmpty(state.InputAudioSync))
 7219            {
 07220                inputModifier += " -async " + state.InputAudioSync;
 7221            }
 7222
 7223            // The -fps_mode option cannot be applied to input
 07224            if (!string.IsNullOrEmpty(state.InputVideoSync) && _mediaEncoder.EncoderVersion < new Version(5, 1))
 7225            {
 07226                inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion);
 7227            }
 7228
 07229            if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp)
 7230            {
 07231                inputModifier += " -re";
 7232            }
 07233            else if (encodingOptions.EnableSegmentDeletion
 07234                && state.VideoStream is not null
 07235                && state.TranscodingType == TranscodingJobType.Hls
 07236                && IsCopyCodec(state.OutputVideoCodec)
 07237                && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateOption)
 7238            {
 7239                // Set an input read rate limit 10x for using SegmentDeletion with stream-copy
 7240                // to prevent ffmpeg from exiting prematurely (due to fast drive)
 07241                inputModifier += " -readrate 10";
 7242            }
 7243
 07244            var flags = new List<string>();
 07245            if (state.IgnoreInputDts)
 7246            {
 07247                flags.Add("+igndts");
 7248            }
 7249
 07250            if (state.IgnoreInputIndex)
 7251            {
 07252                flags.Add("+ignidx");
 7253            }
 7254
 07255            if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
 7256            {
 07257                flags.Add("+genpts");
 7258            }
 7259
 07260            if (state.DiscardCorruptFramesInput)
 7261            {
 07262                flags.Add("+discardcorrupt");
 7263            }
 7264
 07265            if (state.EnableFastSeekInput)
 7266            {
 07267                flags.Add("+fastseek");
 7268            }
 7269
 07270            if (flags.Count > 0)
 7271            {
 07272                inputModifier += " -fflags " + string.Join(string.Empty, flags);
 7273            }
 7274
 07275            if (state.IsVideoRequest)
 7276            {
 07277                if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && encodingOpt
 7278                {
 07279                    var inputFormat = GetInputFormat(state.InputContainer);
 07280                    if (!string.IsNullOrEmpty(inputFormat))
 7281                    {
 07282                        inputModifier += " -f " + inputFormat;
 7283                    }
 7284                }
 7285            }
 7286
 07287            if (state.MediaSource.RequiresLooping)
 7288            {
 07289                inputModifier += " -stream_loop -1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2";
 7290            }
 7291
 07292            return inputModifier;
 7293        }
 7294
 7295        public void AttachMediaSourceInfo(
 7296            EncodingJobInfo state,
 7297            EncodingOptions encodingOptions,
 7298            MediaSourceInfo mediaSource,
 7299            string requestedUrl)
 7300        {
 07301            ArgumentNullException.ThrowIfNull(state);
 7302
 07303            ArgumentNullException.ThrowIfNull(mediaSource);
 7304
 07305            var path = mediaSource.Path;
 07306            var protocol = mediaSource.Protocol;
 7307
 07308            if (!string.IsNullOrEmpty(mediaSource.EncoderPath) && mediaSource.EncoderProtocol.HasValue)
 7309            {
 07310                path = mediaSource.EncoderPath;
 07311                protocol = mediaSource.EncoderProtocol.Value;
 7312            }
 7313
 07314            state.MediaPath = path;
 07315            state.InputProtocol = protocol;
 07316            state.InputContainer = mediaSource.Container;
 07317            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07318            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 7319
 07320            state.IsoType = mediaSource.IsoType;
 7321
 07322            if (mediaSource.Timestamp.HasValue)
 7323            {
 07324                state.InputTimestamp = mediaSource.Timestamp.Value;
 7325            }
 7326
 07327            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07328            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 07329            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
 7330
 07331            if ((state.ReadInputAtNativeFramerate && !state.IsSegmentedLiveStream)
 07332                || (mediaSource.Protocol == MediaProtocol.File
 07333                && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)))
 7334            {
 07335                state.InputVideoSync = "-1";
 07336                state.InputAudioSync = "1";
 7337            }
 7338
 07339            if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase)
 07340                || string.Equals(mediaSource.Container, "asf", StringComparison.OrdinalIgnoreCase))
 7341            {
 7342                // Seeing some stuttering when transcoding wma to audio-only HLS
 07343                state.InputAudioSync = "1";
 7344            }
 7345
 07346            var mediaStreams = mediaSource.MediaStreams;
 7347
 07348            if (state.IsVideoRequest)
 7349            {
 07350                var videoRequest = state.BaseRequest;
 7351
 07352                if (string.IsNullOrEmpty(videoRequest.VideoCodec))
 7353                {
 07354                    if (string.IsNullOrEmpty(requestedUrl))
 7355                    {
 07356                        requestedUrl = "test." + videoRequest.Container;
 7357                    }
 7358
 07359                    videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
 7360                }
 7361
 07362                state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
 07363                state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Su
 07364                state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
 07365                state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
 7366
 07367                if (state.SubtitleStream is not null && !state.SubtitleStream.IsExternal)
 7368                {
 07369                    state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !
 7370                }
 7371
 07372                EnforceResolutionLimit(state);
 7373
 07374                NormalizeSubtitleEmbed(state);
 7375            }
 7376            else
 7377            {
 07378                state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
 7379            }
 7380
 07381            state.MediaSource = mediaSource;
 7382
 07383            var request = state.BaseRequest;
 07384            var supportedAudioCodecs = state.SupportedAudioCodecs;
 07385            if (request is not null && supportedAudioCodecs is not null && supportedAudioCodecs.Length > 0)
 7386            {
 07387                var supportedAudioCodecsList = supportedAudioCodecs.ToList();
 7388
 07389                ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
 7390
 07391                state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray();
 7392
 07393                request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(_mediaEncoder.CanEncodeToAudioCodec)
 07394                    ?? state.SupportedAudioCodecs.FirstOrDefault();
 7395            }
 7396
 07397            var supportedVideoCodecs = state.SupportedVideoCodecs;
 07398            if (request is not null && supportedVideoCodecs is not null && supportedVideoCodecs.Length > 0)
 7399            {
 07400                var supportedVideoCodecsList = supportedVideoCodecs.ToList();
 7401
 07402                ShiftVideoCodecsIfNeeded(supportedVideoCodecsList, encodingOptions);
 7403
 07404                state.SupportedVideoCodecs = supportedVideoCodecsList.ToArray();
 7405
 07406                request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
 7407            }
 07408        }
 7409
 7410        private void ShiftAudioCodecsIfNeeded(List<string> audioCodecs, MediaStream audioStream)
 7411        {
 7412            // No need to shift if there is only one supported audio codec.
 07413            if (audioCodecs.Count < 2)
 7414            {
 07415                return;
 7416            }
 7417
 07418            var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
 07419            var shiftAudioCodecs = new List<string>();
 07420            if (inputChannels >= 6)
 7421            {
 7422                // DTS and TrueHD are not supported by HLS
 7423                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07424                shiftAudioCodecs.Add("dts");
 07425                shiftAudioCodecs.Add("truehd");
 7426            }
 7427            else
 7428            {
 7429                // Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
 7430                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07431                shiftAudioCodecs.Add("ac3");
 07432                shiftAudioCodecs.Add("eac3");
 7433            }
 7434
 07435            if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7436            {
 07437                return;
 7438            }
 7439
 07440            while (shiftAudioCodecs.Contains(audioCodecs[0], StringComparison.OrdinalIgnoreCase))
 7441            {
 07442                var removed = audioCodecs[0];
 07443                audioCodecs.RemoveAt(0);
 07444                audioCodecs.Add(removed);
 7445            }
 07446        }
 7447
 7448        private void ShiftVideoCodecsIfNeeded(List<string> videoCodecs, EncodingOptions encodingOptions)
 7449        {
 7450            // No need to shift if there is only one supported video codec.
 07451            if (videoCodecs.Count < 2)
 7452            {
 07453                return;
 7454            }
 7455
 7456            // Shift codecs to the end of list if it's not allowed.
 07457            var shiftVideoCodecs = new List<string>();
 07458            if (!encodingOptions.AllowHevcEncoding)
 7459            {
 07460                shiftVideoCodecs.Add("hevc");
 07461                shiftVideoCodecs.Add("h265");
 7462            }
 7463
 07464            if (!encodingOptions.AllowAv1Encoding)
 7465            {
 07466                shiftVideoCodecs.Add("av1");
 7467            }
 7468
 07469            if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7470            {
 07471                return;
 7472            }
 7473
 07474            while (shiftVideoCodecs.Contains(videoCodecs[0], StringComparison.OrdinalIgnoreCase))
 7475            {
 07476                var removed = videoCodecs[0];
 07477                videoCodecs.RemoveAt(0);
 07478                videoCodecs.Add(removed);
 7479            }
 07480        }
 7481
 7482        private void NormalizeSubtitleEmbed(EncodingJobInfo state)
 7483        {
 07484            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7485            {
 07486                return;
 7487            }
 7488
 7489            // This is tricky to remux in, after converting to dvdsub it's not positioned correctly
 7490            // Therefore, let's just burn it in
 07491            if (string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 7492            {
 07493                state.SubtitleDeliveryMethod = SubtitleDeliveryMethod.Encode;
 7494            }
 07495        }
 7496
 7497        public string GetSubtitleEmbedArguments(EncodingJobInfo state)
 7498        {
 07499            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7500            {
 07501                return string.Empty;
 7502            }
 7503
 07504            var format = state.SupportedSubtitleCodecs.FirstOrDefault();
 7505            string codec;
 7506
 07507            if (string.IsNullOrEmpty(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.Ordin
 7508            {
 07509                codec = "copy";
 7510            }
 7511            else
 7512            {
 07513                codec = format;
 7514            }
 7515
 07516            return " -codec:s:0 " + codec + " -disposition:s:0 default";
 7517        }
 7518
 7519        public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, Encoder
 7520        {
 7521            // Get the output codec name
 07522            var videoCodec = GetVideoEncoder(state, encodingOptions);
 7523
 07524            var format = string.Empty;
 07525            var keyFrame = string.Empty;
 07526            var outputPath = state.OutputFilePath;
 7527
 07528            if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase)
 07529                && state.BaseRequest.Context == EncodingContext.Streaming)
 7530            {
 7531                // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
 07532                format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov";
 7533            }
 7534
 07535            var threads = GetNumberOfThreads(state, encodingOptions, videoCodec);
 7536
 07537            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7538
 07539            return string.Format(
 07540                CultureInfo.InvariantCulture,
 07541                "{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
 07542                inputModifier,
 07543                GetInputArgument(state, encodingOptions, null),
 07544                keyFrame,
 07545                GetMapArgs(state),
 07546                GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset),
 07547                threads,
 07548                GetProgressiveVideoAudioArguments(state, encodingOptions),
 07549                GetSubtitleEmbedArguments(state),
 07550                format,
 07551                outputPath).Trim();
 7552        }
 7553
 7554        public string GetOutputFFlags(EncodingJobInfo state)
 7555        {
 07556            var flags = new List<string>();
 07557            if (state.GenPtsOutput)
 7558            {
 07559                flags.Add("+genpts");
 7560            }
 7561
 07562            if (flags.Count > 0)
 7563            {
 07564                return " -fflags " + string.Join(string.Empty, flags);
 7565            }
 7566
 07567            return string.Empty;
 7568        }
 7569
 7570        public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoC
 7571        {
 07572            var args = "-codec:v:0 " + videoCodec;
 7573
 07574            if (state.BaseRequest.EnableMpegtsM2TsMode)
 7575            {
 07576                args += " -mpegts_m2ts_mode 1";
 7577            }
 7578
 07579            if (IsCopyCodec(videoCodec))
 7580            {
 07581                if (state.VideoStream is not null
 07582                    && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
 07583                    && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
 7584                {
 07585                    string bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Video);
 07586                    if (!string.IsNullOrEmpty(bitStreamArgs))
 7587                    {
 07588                        args += " " + bitStreamArgs;
 7589                    }
 7590                }
 7591
 07592                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7593                {
 07594                    args += " -copyts -avoid_negative_ts disabled -start_at_zero";
 7595                }
 7596
 07597                if (!state.RunTimeTicks.HasValue)
 7598                {
 07599                    args += " -fflags +genpts";
 7600                }
 7601            }
 7602            else
 7603            {
 07604                var keyFrameArg = string.Format(
 07605                    CultureInfo.InvariantCulture,
 07606                    " -force_key_frames \"expr:gte(t,n_forced*{0})\"",
 07607                    5);
 7608
 07609                args += keyFrameArg;
 7610
 07611                var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream &&
 7612
 07613                var hasCopyTs = false;
 7614
 7615                // video processing filters.
 07616                var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
 7617
 07618                var negativeMapArgs = GetNegativeMapArgsByFilters(state, videoProcessParam);
 7619
 07620                args = negativeMapArgs + args + videoProcessParam;
 7621
 07622                hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
 7623
 07624                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7625                {
 07626                    if (!hasCopyTs)
 7627                    {
 07628                        args += " -copyts";
 7629                    }
 7630
 07631                    args += " -avoid_negative_ts disabled";
 7632
 07633                    if (!(state.SubtitleStream is not null && state.SubtitleStream.IsExternal && !state.SubtitleStream.I
 7634                    {
 07635                        args += " -start_at_zero";
 7636                    }
 7637                }
 7638
 07639                var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset);
 7640
 07641                if (!string.IsNullOrEmpty(qualityParam))
 7642                {
 07643                    args += " " + qualityParam.Trim();
 7644                }
 7645            }
 7646
 07647            if (!string.IsNullOrEmpty(state.OutputVideoSync))
 7648            {
 07649                args += GetVideoSyncOption(state.OutputVideoSync, _mediaEncoder.EncoderVersion);
 7650            }
 7651
 07652            args += GetOutputFFlags(state);
 7653
 07654            return args;
 7655        }
 7656
 7657        public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions)
 7658        {
 7659            // If the video doesn't have an audio stream, return a default.
 07660            if (state.AudioStream is null && state.VideoStream is not null)
 7661            {
 07662                return string.Empty;
 7663            }
 7664
 7665            // Get the output codec name
 07666            var codec = GetAudioEncoder(state);
 7667
 07668            var args = "-codec:a:0 " + codec;
 7669
 07670            if (IsCopyCodec(codec))
 7671            {
 07672                return args;
 7673            }
 7674
 07675            var channels = state.OutputAudioChannels;
 7676
 07677            var useDownMixAlgorithm = state.AudioStream is not null
 07678                                      && DownMixAlgorithmsHelper.AlgorithmFilterStrings.ContainsKey((encodingOptions.Dow
 7679
 07680            if (channels.HasValue && !useDownMixAlgorithm)
 7681            {
 07682                args += " -ac " + channels.Value;
 7683            }
 7684
 07685            var bitrate = state.OutputAudioBitrate;
 07686            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
 7687            {
 07688                var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2);
 07689                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7690                {
 07691                    args += vbrParam;
 7692                }
 7693                else
 7694                {
 07695                    args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
 7696                }
 7697            }
 7698
 07699            if (state.OutputAudioSampleRate.HasValue)
 7700            {
 07701                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
 7702            }
 7703
 07704            args += GetAudioFilterParam(state, encodingOptions);
 7705
 07706            return args;
 7707        }
 7708
 7709        public string GetProgressiveAudioFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string 
 7710        {
 07711            var audioTranscodeParams = new List<string>();
 7712
 07713            var bitrate = state.OutputAudioBitrate;
 07714            var channels = state.OutputAudioChannels;
 07715            var outputCodec = state.OutputAudioCodec;
 7716
 07717            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
 7718            {
 07719                var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2);
 07720                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7721                {
 07722                    audioTranscodeParams.Add(vbrParam);
 7723                }
 7724                else
 7725                {
 07726                    audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
 7727                }
 7728            }
 7729
 07730            if (channels.HasValue)
 7731            {
 07732                audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)
 7733            }
 7734
 07735            if (!string.IsNullOrEmpty(outputCodec))
 7736            {
 07737                audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
 7738            }
 7739
 07740            if (GetAudioEncoder(state).StartsWith("pcm_", StringComparison.Ordinal))
 7741            {
 07742                audioTranscodeParams.Add(string.Concat("-f ", GetAudioEncoder(state).AsSpan(4)));
 07743                audioTranscodeParams.Add("-ar " + state.BaseRequest.AudioBitRate);
 7744            }
 7745
 07746            if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
 7747            {
 7748                // opus only supports specific sampling rates
 07749                var sampleRate = state.OutputAudioSampleRate;
 07750                if (sampleRate.HasValue)
 7751                {
 07752                    var sampleRateValue = sampleRate.Value switch
 07753                    {
 07754                        <= 8000 => 8000,
 07755                        <= 12000 => 12000,
 07756                        <= 16000 => 16000,
 07757                        <= 24000 => 24000,
 07758                        _ => 48000
 07759                    };
 7760
 07761                    audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
 7762                }
 7763            }
 7764
 7765            // Copy the movflags from GetProgressiveVideoFullCommandLine
 7766            // See #9248 and the associated PR for why this is needed
 07767            if (_mp4ContainerNames.Contains(state.OutputContainer))
 7768            {
 07769                audioTranscodeParams.Add("-movflags empty_moov+delay_moov");
 7770            }
 7771
 07772            var threads = GetNumberOfThreads(state, encodingOptions, null);
 7773
 07774            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7775
 07776            return string.Format(
 07777                CultureInfo.InvariantCulture,
 07778                "{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"",
 07779                inputModifier,
 07780                GetInputArgument(state, encodingOptions, null),
 07781                threads,
 07782                " -vn",
 07783                string.Join(' ', audioTranscodeParams),
 07784                outputPath,
 07785                string.Empty,
 07786                string.Empty,
 07787                string.Empty).Trim();
 7788        }
 7789
 7790        public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
 7791        {
 07792            var index = 0;
 07793            var length = mediaStreams.Count;
 7794
 07795            for (var i = 0; i < length; i++)
 7796            {
 07797                var currentMediaStream = mediaStreams[i];
 07798                if (currentMediaStream == streamToFind)
 7799                {
 07800                    return index;
 7801                }
 7802
 07803                if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
 7804                {
 07805                    index++;
 7806                }
 7807            }
 7808
 07809            return -1;
 7810        }
 7811
 7812        public static bool IsCopyCodec(string codec)
 7813        {
 07814            return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
 7815        }
 7816
 7817        private static bool ShouldEncodeSubtitle(EncodingJobInfo state)
 7818        {
 07819            return state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
 07820                   || (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec));
 7821        }
 7822
 7823        public static string GetVideoSyncOption(string videoSync, Version encoderVersion)
 7824        {
 07825            if (string.IsNullOrEmpty(videoSync))
 7826            {
 07827                return string.Empty;
 7828            }
 7829
 07830            if (encoderVersion >= new Version(5, 1))
 7831            {
 07832                if (int.TryParse(videoSync, CultureInfo.InvariantCulture, out var vsync))
 7833                {
 07834                    return vsync switch
 07835                    {
 07836                        -1 => " -fps_mode auto",
 07837                        0 => " -fps_mode passthrough",
 07838                        1 => " -fps_mode cfr",
 07839                        2 => " -fps_mode vfr",
 07840                        _ => string.Empty
 07841                    };
 7842                }
 7843
 07844                return string.Empty;
 7845            }
 7846
 7847            // -vsync is deprecated in FFmpeg 5.1 and will be removed in the future.
 07848            return $" -vsync {videoSync}";
 7849        }
 7850    }
 7851}

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,MediaBrowser.Model.Configuration.EncodingOptions)
GetFfmpegAnalyzeDurationArg(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetFfmpegProbesizeArg()
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)