< 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: 3708
Coverable lines: 3734
Total lines: 7818
Line coverage: 0.6%
Branch coverage
0%
Covered branches: 0
Total branches: 3729
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 10/25/2025 - 12:09:58 AM Line coverage: 0.6% (26/3722) Branch coverage: 0% (0/3701) Total lines: 778210/28/2025 - 12:11:27 AM Line coverage: 0.6% (26/3722) Branch coverage: 0% (0/3703) Total lines: 778212/4/2025 - 12:11:49 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: 7818 10/25/2025 - 12:09:58 AM Line coverage: 0.6% (26/3722) Branch coverage: 0% (0/3701) Total lines: 778210/28/2025 - 12:11:27 AM Line coverage: 0.6% (26/3722) Branch coverage: 0% (0/3703) Total lines: 778212/4/2025 - 12:11:49 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: 7818

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%1190340%
IsH264(...)0%2040%
IsH265(...)0%2040%
IsAv1(...)0%620%
IsAAC(...)0%620%
IsDoviWithHdr10Bl(...)0%7280%
IsDovi(...)0%110100%
IsHdr10Plus(...)0%7280%
ShouldRemoveDynamicHdrMetadata(...)0%600240%
CanEncoderRemoveDynamicHdrMetadata(...)0%342180%
IsDoviRemoved(...)0%4260%
IsHdr10PlusRemoved(...)0%4260%
GetBitStreamArgs(...)0%702260%
GetAudioBitStreamArguments(...)0%110100%
GetSegmentFileExtension(...)0%620%
GetVideoBitrateParam(...)0%1482380%
GetEncoderParam(...)0%8010890%
NormalizeTranscodingLevel(...)0%506220%
GetTextSubtitlesFilter(...)0%210140%
GetFramerateParam(...)0%156120%
GetHlsVideoKeyFrameArguments(...)0%1806420%
GetVideoQualityParam(...)0%325801800%
CanStreamCopyVideo(...)0%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%
GetInputModifier(...)0%3906620%
AttachMediaSourceInfo(...)0%1806420%
ShiftAudioCodecsIfNeeded(...)0%110100%
ShiftVideoCodecsIfNeeded(...)0%110100%
NormalizeSubtitleEmbed(...)0%4260%
GetSubtitleEmbedArguments(...)0%7280%
GetProgressiveVideoFullCommandLine(...)0%2040%
GetOutputFFlags(...)0%2040%
GetProgressiveVideoArguments(...)0%1482380%
GetProgressiveVideoAudioArguments(...)0%600240%
GetProgressiveAudioFullCommandLine(...)0%930300%
FindIndex(...)0%4260%
IsCopyCodec(...)100%210%
ShouldEncodeSubtitle(...)0%2040%
GetVideoSyncOption(...)0%132110%

File(s)

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

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CS1591
 4// We need lowercase normalized string for ffmpeg
 5#pragma warning disable CA1308
 6
 7using System;
 8using System.Collections.Generic;
 9using System.Globalization;
 10using System.IO;
 11using System.Linq;
 12using System.Runtime.InteropServices;
 13using System.Text;
 14using System.Text.RegularExpressions;
 15using System.Threading;
 16using Jellyfin.Data;
 17using Jellyfin.Data.Enums;
 18using Jellyfin.Database.Implementations.Enums;
 19using Jellyfin.Extensions;
 20using MediaBrowser.Common.Configuration;
 21using MediaBrowser.Controller.Extensions;
 22using MediaBrowser.Controller.IO;
 23using MediaBrowser.Model.Configuration;
 24using MediaBrowser.Model.Dlna;
 25using MediaBrowser.Model.Dto;
 26using MediaBrowser.Model.Entities;
 27using MediaBrowser.Model.MediaInfo;
 28using Microsoft.Extensions.Configuration;
 29using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
 30
 31namespace MediaBrowser.Controller.MediaEncoding
 32{
 33    public partial class EncodingHelper
 34    {
 35        /// <summary>
 36        /// The codec validation regex.
 37        /// This regular expression matches strings that consist of alphanumeric characters, hyphens,
 38        /// periods, underscores, commas, and vertical bars, with a length between 0 and 40 characters.
 39        /// This should matches all common valid codecs.
 40        /// </summary>
 41        public const string ContainerValidationRegex = @"^[a-zA-Z0-9\-\._,|]{0,40}$";
 42
 43        /// <summary>
 44        /// The level validation regex.
 45        /// This regular expression matches strings representing a double.
 46        /// </summary>
 47        public const string LevelValidationRegex = @"-?[0-9]+(?:\.[0-9]+)?";
 48
 49        private const string _defaultMjpegEncoder = "mjpeg";
 50
 51        private const string QsvAlias = "qs";
 52        private const string VaapiAlias = "va";
 53        private const string D3d11vaAlias = "dx11";
 54        private const string VideotoolboxAlias = "vt";
 55        private const string RkmppAlias = "rk";
 56        private const string OpenclAlias = "ocl";
 57        private const string CudaAlias = "cu";
 58        private const string DrmAlias = "dr";
 59        private const string VulkanAlias = "vk";
 60        private readonly IApplicationPaths _appPaths;
 61        private readonly IMediaEncoder _mediaEncoder;
 62        private readonly ISubtitleEncoder _subtitleEncoder;
 63        private readonly IConfiguration _config;
 64        private readonly IConfigurationManager _configurationManager;
 65        private readonly IPathManager _pathManager;
 66
 67        // i915 hang was fixed by linux 6.2 (3f882f2)
 2168        private readonly Version _minKerneli915Hang = new Version(5, 18);
 2169        private readonly Version _maxKerneli915Hang = new Version(6, 1, 3);
 2170        private readonly Version _minFixedKernel60i915Hang = new Version(6, 0, 18);
 2171        private readonly Version _minKernelVersionAmdVkFmtModifier = new Version(5, 15);
 72
 2173        private readonly Version _minFFmpegImplicitHwaccel = new Version(6, 0);
 2174        private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0);
 2175        private readonly Version _minFFmpegOclCuTonemapMode = new Version(5, 1, 3);
 2176        private readonly Version _minFFmpegSvtAv1Params = new Version(5, 1);
 2177        private readonly Version _minFFmpegVaapiH26xEncA53CcSei = new Version(6, 0);
 2178        private readonly Version _minFFmpegReadrateOption = new Version(5, 0);
 2179        private readonly Version _minFFmpegWorkingVtHwSurface = new Version(7, 0, 1);
 2180        private readonly Version _minFFmpegDisplayRotationOption = new Version(6, 0);
 2181        private readonly Version _minFFmpegAdvancedTonemapMode = new Version(7, 0, 1);
 2182        private readonly Version _minFFmpegAlteredVaVkInterop = new Version(7, 0, 1);
 2183        private readonly Version _minFFmpegQsvVppTonemapOption = new Version(7, 0, 1);
 2184        private readonly Version _minFFmpegQsvVppOutRangeOption = new Version(7, 0, 1);
 2185        private readonly Version _minFFmpegVaapiDeviceVendorId = new Version(7, 0, 1);
 2186        private readonly Version _minFFmpegQsvVppScaleModeOption = new Version(6, 0);
 2187        private readonly Version _minFFmpegRkmppHevcDecDoviRpu = new Version(7, 1, 1);
 88
 089        private static readonly Regex _containerValidationRegex = new(ContainerValidationRegex, RegexOptions.Compiled);
 90
 091        private static readonly string[] _videoProfilesH264 =
 092        [
 093            "ConstrainedBaseline",
 094            "Baseline",
 095            "Extended",
 096            "Main",
 097            "High",
 098            "ProgressiveHigh",
 099            "ConstrainedHigh",
 0100            "High10"
 0101        ];
 102
 0103        private static readonly string[] _videoProfilesH265 =
 0104        [
 0105            "Main",
 0106            "Main10"
 0107        ];
 108
 0109        private static readonly string[] _videoProfilesAv1 =
 0110        [
 0111            "Main",
 0112            "High",
 0113            "Professional",
 0114        ];
 115
 0116        private static readonly HashSet<string> _mp4ContainerNames = new(StringComparer.OrdinalIgnoreCase)
 0117        {
 0118            "mp4",
 0119            "m4a",
 0120            "m4p",
 0121            "m4b",
 0122            "m4r",
 0123            "m4v",
 0124        };
 125
 0126        private static readonly TonemappingMode[] _legacyTonemapModes = [TonemappingMode.max, TonemappingMode.rgb];
 0127        private static readonly TonemappingMode[] _advancedTonemapModes = [TonemappingMode.lum, TonemappingMode.itp];
 128
 129        // Set max transcoding channels for encoders that can't handle more than a set amount of channels
 130        // AAC, FLAC, ALAC, libopus, libvorbis encoders all support at least 8 channels
 0131        private static readonly Dictionary<string, int> _audioTranscodeChannelLookup = new(StringComparer.OrdinalIgnoreC
 0132        {
 0133            { "libmp3lame", 2 },
 0134            { "libfdk_aac", 6 },
 0135            { "ac3", 6 },
 0136            { "eac3", 6 },
 0137            { "dca", 6 },
 0138            { "mlp", 6 },
 0139            { "truehd", 6 },
 0140        };
 141
 0142        private static readonly Dictionary<HardwareAccelerationType, string> _mjpegCodecMap = new()
 0143        {
 0144            { HardwareAccelerationType.vaapi, _defaultMjpegEncoder + "_vaapi" },
 0145            { HardwareAccelerationType.qsv, _defaultMjpegEncoder + "_qsv" },
 0146            { HardwareAccelerationType.videotoolbox, _defaultMjpegEncoder + "_videotoolbox" },
 0147            { HardwareAccelerationType.rkmpp, _defaultMjpegEncoder + "_rkmpp" }
 0148        };
 149
 0150        public static readonly string[] LosslessAudioCodecs =
 0151        [
 0152            "alac",
 0153            "ape",
 0154            "flac",
 0155            "mlp",
 0156            "truehd",
 0157            "wavpack"
 0158        ];
 159
 160        public EncodingHelper(
 161            IApplicationPaths appPaths,
 162            IMediaEncoder mediaEncoder,
 163            ISubtitleEncoder subtitleEncoder,
 164            IConfiguration config,
 165            IConfigurationManager configurationManager,
 166            IPathManager pathManager)
 167        {
 21168            _appPaths = appPaths;
 21169            _mediaEncoder = mediaEncoder;
 21170            _subtitleEncoder = subtitleEncoder;
 21171            _config = config;
 21172            _configurationManager = configurationManager;
 21173            _pathManager = pathManager;
 21174        }
 175
 176        private enum DynamicHdrMetadataRemovalPlan
 177        {
 178            None,
 179            RemoveDovi,
 180            RemoveHdr10Plus,
 181        }
 182
 183        [GeneratedRegex(@"\s+")]
 184        private static partial Regex WhiteSpaceRegex();
 185
 186        public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0187            => GetH26xOrAv1Encoder("libx264", "h264", state, encodingOptions);
 188
 189        public string GetH265Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0190            => GetH26xOrAv1Encoder("libx265", "hevc", state, encodingOptions);
 191
 192        public string GetAv1Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0193            => GetH26xOrAv1Encoder("libsvtav1", "av1", state, encodingOptions);
 194
 195        private string GetH26xOrAv1Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptio
 196        {
 197            // Only use alternative encoders for video files.
 198            // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying
 199            // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such 
 0200            if (state.VideoType == VideoType.VideoFile)
 201            {
 0202                var hwType = encodingOptions.HardwareAccelerationType;
 203
 0204                var codecMap = new Dictionary<HardwareAccelerationType, string>()
 0205                {
 0206                    { HardwareAccelerationType.amf,                  hwEncoder + "_amf" },
 0207                    { HardwareAccelerationType.nvenc,                hwEncoder + "_nvenc" },
 0208                    { HardwareAccelerationType.qsv,                  hwEncoder + "_qsv" },
 0209                    { HardwareAccelerationType.vaapi,                hwEncoder + "_vaapi" },
 0210                    { HardwareAccelerationType.videotoolbox,         hwEncoder + "_videotoolbox" },
 0211                    { HardwareAccelerationType.v4l2m2m,              hwEncoder + "_v4l2m2m" },
 0212                    { HardwareAccelerationType.rkmpp,                hwEncoder + "_rkmpp" },
 0213                };
 214
 0215                if (hwType != HardwareAccelerationType.none
 0216                    && encodingOptions.EnableHardwareEncoding
 0217                    && codecMap.TryGetValue(hwType, out var preferredEncoder)
 0218                    && _mediaEncoder.SupportsEncoder(preferredEncoder))
 219                {
 0220                    return preferredEncoder;
 221                }
 222            }
 223
 0224            return defaultEncoder;
 225        }
 226
 227        private string GetMjpegEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 228        {
 0229            if (state.VideoType == VideoType.VideoFile)
 230            {
 0231                var hwType = encodingOptions.HardwareAccelerationType;
 232
 233                // Only 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                // Also seek the external subtitles stream.
 01271                var seekSubParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01272                if (!string.IsNullOrEmpty(seekSubParam))
 1273                {
 01274                    arg.Append(' ').Append(seekSubParam);
 1275                }
 1276
 01277                if (!string.IsNullOrEmpty(canvasArgs))
 1278                {
 01279                    arg.Append(canvasArgs);
 1280                }
 1281
 01282                arg.Append(" -i file:\"").Append(subtitlePath).Append('\"');
 1283            }
 1284
 01285            if (state.AudioStream is not null && state.AudioStream.IsExternal)
 1286            {
 1287                // Also seek the external audio stream.
 01288                var seekAudioParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01289                if (!string.IsNullOrEmpty(seekAudioParam))
 1290                {
 01291                    arg.Append(' ').Append(seekAudioParam);
 1292                }
 1293
 01294                arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
 1295            }
 1296
 1297            // Disable auto inserted SW scaler for HW decoders in case of changed resolution.
 01298            var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
 01299            if (!isSwDecoder)
 1300            {
 01301                arg.Append(" -noautoscale");
 1302            }
 1303
 01304            return arg.ToString();
 1305        }
 1306
 1307        /// <summary>
 1308        /// Determines whether the specified stream is H264.
 1309        /// </summary>
 1310        /// <param name="stream">The stream.</param>
 1311        /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
 1312        public static bool IsH264(MediaStream stream)
 1313        {
 01314            var codec = stream.Codec ?? string.Empty;
 1315
 01316            return codec.Contains("264", StringComparison.OrdinalIgnoreCase)
 01317                    || codec.Contains("avc", StringComparison.OrdinalIgnoreCase);
 1318        }
 1319
 1320        public static bool IsH265(MediaStream stream)
 1321        {
 01322            var codec = stream.Codec ?? string.Empty;
 1323
 01324            return codec.Contains("265", StringComparison.OrdinalIgnoreCase)
 01325                || codec.Contains("hevc", StringComparison.OrdinalIgnoreCase);
 1326        }
 1327
 1328        public static bool IsAv1(MediaStream stream)
 1329        {
 01330            var codec = stream.Codec ?? string.Empty;
 1331
 01332            return codec.Contains("av1", StringComparison.OrdinalIgnoreCase);
 1333        }
 1334
 1335        public static bool IsAAC(MediaStream stream)
 1336        {
 01337            var codec = stream.Codec ?? string.Empty;
 1338
 01339            return codec.Contains("aac", StringComparison.OrdinalIgnoreCase);
 1340        }
 1341
 1342        public static bool IsDoviWithHdr10Bl(MediaStream stream)
 1343        {
 01344            var rangeType = stream?.VideoRangeType;
 1345
 01346            return rangeType is VideoRangeType.DOVIWithHDR10
 01347                or VideoRangeType.DOVIWithEL
 01348                or VideoRangeType.DOVIWithHDR10Plus
 01349                or VideoRangeType.DOVIWithELHDR10Plus
 01350                or VideoRangeType.DOVIInvalid;
 1351        }
 1352
 1353        public static bool IsDovi(MediaStream stream)
 1354        {
 01355            var rangeType = stream?.VideoRangeType;
 1356
 01357            return IsDoviWithHdr10Bl(stream)
 01358                   || (rangeType is VideoRangeType.DOVI
 01359                       or VideoRangeType.DOVIWithHLG
 01360                       or VideoRangeType.DOVIWithSDR);
 1361        }
 1362
 1363        public static bool IsHdr10Plus(MediaStream stream)
 1364        {
 01365            var rangeType = stream?.VideoRangeType;
 1366
 01367            return rangeType is VideoRangeType.HDR10Plus
 01368                       or VideoRangeType.DOVIWithHDR10Plus
 01369                       or VideoRangeType.DOVIWithELHDR10Plus;
 1370        }
 1371
 1372        /// <summary>
 1373        /// Check if dynamic HDR metadata should be removed during stream copy.
 1374        /// Please note this check assumes the range check has already been done
 1375        /// and trivial fallbacks like HDR10+ to HDR10, DOVIWithHDR10 to HDR10 is already checked.
 1376        /// </summary>
 1377        private static DynamicHdrMetadataRemovalPlan ShouldRemoveDynamicHdrMetadata(EncodingJobInfo state)
 1378        {
 01379            var videoStream = state.VideoStream;
 01380            if (videoStream.VideoRange is not VideoRange.HDR)
 1381            {
 01382                return DynamicHdrMetadataRemovalPlan.None;
 1383            }
 1384
 01385            var requestedRangeTypes = state.GetRequestedRangeTypes(state.VideoStream.Codec);
 01386            if (requestedRangeTypes.Length == 0)
 1387            {
 01388                return DynamicHdrMetadataRemovalPlan.None;
 1389            }
 1390
 01391            var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ordinal
 01392            var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIg
 01393            var requestHasDOVIwithEL = requestedRangeTypes.Contains(VideoRangeType.DOVIWithEL.ToString(), StringComparis
 01394            var requestHasDOVIwithELHDR10plus = requestedRangeTypes.Contains(VideoRangeType.DOVIWithELHDR10Plus.ToString
 1395
 01396            var shouldRemoveHdr10Plus = false;
 1397            // Case 1: Client supports HDR10, does not support DOVI with EL but EL presets
 01398            var shouldRemoveDovi = (!requestHasDOVIwithEL && requestHasHDR10) && videoStream.VideoRangeType == VideoRang
 1399
 1400            // Case 2: Client supports DOVI, does not support broken DOVI config
 1401            // Client does not report DOVI support should be allowed to copy bad data for remuxing as HDR10 players woul
 01402            shouldRemoveDovi = shouldRemoveDovi || (requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVII
 1403
 1404            // Special case: we have a video with both EL and HDR10+
 1405            // If the client supports EL but not in the case of coexistence with HDR10+, remove HDR10+ for compatibility
 1406            // Otherwise, remove DOVI if the client is not a DOVI player
 01407            if (videoStream.VideoRangeType == VideoRangeType.DOVIWithELHDR10Plus)
 1408            {
 01409                shouldRemoveHdr10Plus = requestHasDOVIwithEL && !requestHasDOVIwithELHDR10plus;
 01410                shouldRemoveDovi = shouldRemoveDovi || !shouldRemoveHdr10Plus;
 1411            }
 1412
 01413            if (shouldRemoveDovi)
 1414            {
 01415                return DynamicHdrMetadataRemovalPlan.RemoveDovi;
 1416            }
 1417
 1418            // If the client is a Dolby Vision Player, remove the HDR10+ metadata to avoid playback issues
 01419            shouldRemoveHdr10Plus = shouldRemoveHdr10Plus || (requestHasDOVI && videoStream.VideoRangeType == VideoRange
 01420            return shouldRemoveHdr10Plus ? DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus : DynamicHdrMetadataRemovalPlan
 1421        }
 1422
 1423        private bool CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan plan, MediaStream videoStream)
 1424        {
 01425            return plan switch
 01426            {
 01427                DynamicHdrMetadataRemovalPlan.RemoveDovi => _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFil
 01428                                                            || (IsH265(videoStream) && _mediaEncoder.SupportsBitStreamFi
 01429                                                            || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStreamFil
 01430                DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus => (IsH265(videoStream) && _mediaEncoder.SupportsBitStream
 01431                                                                 || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStre
 01432                _ => true,
 01433            };
 1434        }
 1435
 1436        public bool IsDoviRemoved(EncodingJobInfo state)
 1437        {
 01438            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01439                                              && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Remove
 1440        }
 1441
 1442        public bool IsHdr10PlusRemoved(EncodingJobInfo state)
 1443        {
 01444            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01445                                                  && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Re
 1446        }
 1447
 1448        public string GetBitStreamArgs(EncodingJobInfo state, MediaStreamType streamType)
 1449        {
 01450            if (state is null)
 1451            {
 01452                return null;
 1453            }
 1454
 01455            var stream = streamType switch
 01456            {
 01457                MediaStreamType.Audio => state.AudioStream,
 01458                MediaStreamType.Video => state.VideoStream,
 01459                _ => state.VideoStream
 01460            };
 1461            // TODO This is auto inserted into the mpegts mux so it might not be needed.
 1462            // https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
 01463            if (IsH264(stream))
 1464            {
 01465                return "-bsf:v h264_mp4toannexb";
 1466            }
 1467
 01468            if (IsAAC(stream))
 1469            {
 1470                // Convert adts header(mpegts) to asc header(mp4).
 01471                return "-bsf:a aac_adtstoasc";
 1472            }
 1473
 01474            if (IsH265(stream))
 1475            {
 01476                var filter = "-bsf:v hevc_mp4toannexb";
 1477
 1478                // The following checks are not complete because the copy would be rejected
 1479                // if the encoder cannot remove required metadata.
 1480                // And if bsf is used, we must already be using copy codec.
 01481                switch (ShouldRemoveDynamicHdrMetadata(state))
 1482                {
 1483                    default:
 1484                    case DynamicHdrMetadataRemovalPlan.None:
 1485                        break;
 1486                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01487                        filter += _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.HevcMetadata
 01488                            ? ",hevc_metadata=remove_dovi=1"
 01489                            : ",dovi_rpu=strip=1";
 01490                        break;
 1491                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01492                        filter += ",hevc_metadata=remove_hdr10plus=1";
 1493                        break;
 1494                }
 1495
 01496                return filter;
 1497            }
 1498
 01499            if (IsAv1(stream))
 1500            {
 01501                switch (ShouldRemoveDynamicHdrMetadata(state))
 1502                {
 1503                    default:
 1504                    case DynamicHdrMetadataRemovalPlan.None:
 01505                        return null;
 1506                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01507                        return _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.Av1MetadataRemo
 01508                            ? "-bsf:v av1_metadata=remove_dovi=1"
 01509                            : "-bsf:v dovi_rpu=strip=1";
 1510                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01511                        return "-bsf:v av1_metadata=remove_hdr10plus=1";
 1512                }
 1513            }
 1514
 01515            return null;
 1516        }
 1517
 1518        public string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceConta
 1519        {
 01520            var bitStreamArgs = string.Empty;
 01521            var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
 1522
 1523            // Apply aac_adtstoasc bitstream filter when media source is in mpegts.
 01524            if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)
 01525                && (string.Equals(mediaSourceContainer, "ts", StringComparison.OrdinalIgnoreCase)
 01526                    || string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase)
 01527                    || string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase)))
 1528            {
 01529                bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Audio);
 01530                bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs;
 1531            }
 1532
 01533            return bitStreamArgs;
 1534        }
 1535
 1536        public static string GetSegmentFileExtension(string segmentContainer)
 1537        {
 01538            if (!string.IsNullOrWhiteSpace(segmentContainer))
 1539            {
 01540                return "." + segmentContainer;
 1541            }
 1542
 01543            return ".ts";
 1544        }
 1545
 1546        private string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
 1547        {
 01548            if (state.OutputVideoBitrate is null)
 1549            {
 01550                return string.Empty;
 1551            }
 1552
 01553            int bitrate = state.OutputVideoBitrate.Value;
 1554
 1555            // Bit rate under 1000k is not allowed in h264_qsv
 01556            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 1557            {
 01558                bitrate = Math.Max(bitrate, 1000);
 1559            }
 1560
 1561            // Currently use the same buffer size for all encoders
 01562            int bufsize = bitrate * 2;
 1563
 01564            if (string.Equals(videoCodec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1565            {
 01566                return FormattableString.Invariant($" -b:v {bitrate} -bufsize {bufsize}");
 1567            }
 1568
 01569            if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)
 01570                || string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
 1571            {
 01572                return FormattableString.Invariant($" -maxrate {bitrate} -bufsize {bufsize}");
 1573            }
 1574
 01575            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01576                || string.Equals(videoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 01577                || string.Equals(videoCodec, "av1_qsv", StringComparison.OrdinalIgnoreCase))
 1578            {
 1579                // TODO: probe QSV encoders' capabilities and enable more tuning options
 1580                // See also https://github.com/intel/media-delivery/blob/master/doc/quality.rst
 1581
 1582                // Enable MacroBlock level bitrate control for better subjective visual quality
 01583                var mbbrcOpt = string.Empty;
 01584                if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01585                    || string.Equals(videoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 1586                {
 01587                    mbbrcOpt = " -mbbrc 1";
 1588                }
 1589
 1590                // Set (maxrate == bitrate + 1) to trigger VBR for better bitrate allocation
 1591                // Set (rc_init_occupancy == 2 * bitrate) and (bufsize == 4 * bitrate) to deal with drastic scene change
 01592                return FormattableString.Invariant($"{mbbrcOpt} -b:v {bitrate} -maxrate {bitrate + 1} -rc_init_occupancy
 1593            }
 1594
 01595            if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01596                || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01597                || string.Equals(videoCodec, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1598            {
 1599                // Override the too high default qmin 18 in transcoding preset
 01600                return FormattableString.Invariant($" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsiz
 1601            }
 1602
 01603            if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01604                || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01605                || string.Equals(videoCodec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1606            {
 1607                // VBR in i965 driver may result in pixelated output.
 01608                if (_mediaEncoder.IsVaapiDeviceInteli965)
 1609                {
 01610                    return FormattableString.Invariant($" -rc_mode CBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsi
 1611                }
 1612
 01613                return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"
 1614            }
 1615
 01616            if (string.Equals(videoCodec, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 01617                || string.Equals(videoCodec, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase))
 1618            {
 1619                // The `maxrate` and `bufsize` options can potentially lead to performance regression
 1620                // and even encoder hangs, especially when the value is very high.
 01621                return FormattableString.Invariant($" -b:v {bitrate} -qmin -1 -qmax -1");
 1622            }
 1623
 01624            return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
 1625        }
 1626
 1627        private string GetEncoderParam(EncoderPreset? preset, EncoderPreset defaultPreset, EncodingOptions encodingOptio
 1628        {
 01629            var param = string.Empty;
 01630            var encoderPreset = preset ?? defaultPreset;
 01631            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
 1632            {
 01633                var presetString = encoderPreset switch
 01634                {
 01635                    EncoderPreset.auto => EncoderPreset.veryfast.ToString().ToLowerInvariant(),
 01636                    _ => encoderPreset.ToString().ToLowerInvariant()
 01637                };
 1638
 01639                param += " -preset " + presetString;
 1640
 01641                int encodeCrf = encodingOptions.H264Crf;
 01642                if (isLibX265)
 1643                {
 01644                    encodeCrf = encodingOptions.H265Crf;
 1645                }
 1646
 01647                if (encodeCrf >= 0 && encodeCrf <= 51)
 1648                {
 01649                    param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture);
 1650                }
 1651                else
 1652                {
 01653                    string defaultCrf = "23";
 01654                    if (isLibX265)
 1655                    {
 01656                        defaultCrf = "28";
 1657                    }
 1658
 01659                    param += " -crf " + defaultCrf;
 1660                }
 1661            }
 01662            else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1663            {
 1664                // Default to use the recommended preset 10.
 1665                // Omit presets < 5, which are too slow for on the fly encoding.
 1666                // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md
 01667                param += encoderPreset switch
 01668                {
 01669                    EncoderPreset.veryslow => " -preset 5",
 01670                    EncoderPreset.slower => " -preset 6",
 01671                    EncoderPreset.slow => " -preset 7",
 01672                    EncoderPreset.medium => " -preset 8",
 01673                    EncoderPreset.fast => " -preset 9",
 01674                    EncoderPreset.faster => " -preset 10",
 01675                    EncoderPreset.veryfast => " -preset 11",
 01676                    EncoderPreset.superfast => " -preset 12",
 01677                    EncoderPreset.ultrafast => " -preset 13",
 01678                    _ => " -preset 10"
 01679                };
 1680            }
 01681            else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01682                     || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01683                     || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1684            {
 1685                // -compression_level is not reliable on AMD.
 01686                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 1687                {
 01688                    param += encoderPreset switch
 01689                    {
 01690                        EncoderPreset.veryslow => " -compression_level 1",
 01691                        EncoderPreset.slower => " -compression_level 2",
 01692                        EncoderPreset.slow => " -compression_level 3",
 01693                        EncoderPreset.medium => " -compression_level 4",
 01694                        EncoderPreset.fast => " -compression_level 5",
 01695                        EncoderPreset.faster => " -compression_level 6",
 01696                        EncoderPreset.veryfast => " -compression_level 7",
 01697                        EncoderPreset.superfast => " -compression_level 7",
 01698                        EncoderPreset.ultrafast => " -compression_level 7",
 01699                        _ => string.Empty
 01700                    };
 1701                }
 1702            }
 01703            else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv)
 01704                     || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv)
 01705                     || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv)
 1706            {
 01707                EncoderPreset[] valid_presets = [EncoderPreset.veryslow, EncoderPreset.slower, EncoderPreset.slow, Encod
 1708
 01709                param += " -preset " + (valid_presets.Contains(encoderPreset) ? encoderPreset : EncoderPreset.veryfast).
 1710            }
 01711            else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
 01712                        || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_n
 01713                        || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase) // av1 (av1_nven
 01714            )
 1715            {
 01716                param += encoderPreset switch
 01717                {
 01718                        EncoderPreset.veryslow => " -preset p7",
 01719                        EncoderPreset.slower => " -preset p6",
 01720                        EncoderPreset.slow => " -preset p5",
 01721                        EncoderPreset.medium => " -preset p4",
 01722                        EncoderPreset.fast => " -preset p3",
 01723                        EncoderPreset.faster => " -preset p2",
 01724                        _ => " -preset p1"
 01725                };
 1726            }
 01727            else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf)
 01728                        || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf
 01729                        || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase) // av1 (av1_amf)
 01730            )
 1731            {
 01732                param += encoderPreset switch
 01733                {
 01734                        EncoderPreset.veryslow => " -quality quality",
 01735                        EncoderPreset.slower => " -quality quality",
 01736                        EncoderPreset.slow => " -quality quality",
 01737                        EncoderPreset.medium => " -quality balanced",
 01738                        _ => " -quality speed"
 01739                };
 1740
 01741                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01742                    || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1743                {
 01744                    param += " -header_insertion_mode gop";
 1745                }
 1746
 01747                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
 1748                {
 01749                    param += " -gops_per_idr 1";
 1750                }
 1751            }
 01752            else if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase) // h264 (h264_
 01753                        || string.Equals(videoEncoder, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase) // hevc 
 01754            )
 1755            {
 01756                param += encoderPreset switch
 01757                {
 01758                        EncoderPreset.veryslow => " -prio_speed 0",
 01759                        EncoderPreset.slower => " -prio_speed 0",
 01760                        EncoderPreset.slow => " -prio_speed 0",
 01761                        EncoderPreset.medium => " -prio_speed 0",
 01762                        _ => " -prio_speed 1"
 01763                };
 1764            }
 1765
 01766            return param;
 1767        }
 1768
 1769        public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
 1770        {
 01771            if (double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel))
 1772            {
 01773                if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase))
 1774                {
 1775                    // Transcode to level 5.3 (15) and lower for maximum compatibility.
 1776                    // https://en.wikipedia.org/wiki/AV1#Levels
 01777                    if (requestLevel < 0 || requestLevel >= 15)
 1778                    {
 01779                        return "15";
 1780                    }
 1781                }
 01782                else if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 01783                         || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
 1784                {
 1785                    // Transcode to level 5.0 and lower for maximum compatibility.
 1786                    // Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it.
 1787                    // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
 1788                    // MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880.
 01789                    if (requestLevel < 0 || requestLevel >= 150)
 1790                    {
 01791                        return "150";
 1792                    }
 1793                }
 01794                else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
 1795                {
 1796                    // Transcode to level 5.1 and lower for maximum compatibility.
 1797                    // h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
 1798                    // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
 01799                    if (requestLevel < 0 || requestLevel >= 51)
 1800                    {
 01801                        return "51";
 1802                    }
 1803                }
 1804            }
 1805
 01806            return level;
 1807        }
 1808
 1809        /// <summary>
 1810        /// Gets the text subtitle param.
 1811        /// </summary>
 1812        /// <param name="state">The state.</param>
 1813        /// <param name="enableAlpha">Enable alpha processing.</param>
 1814        /// <param name="enableSub2video">Enable sub2video mode.</param>
 1815        /// <returns>System.String.</returns>
 1816        public string GetTextSubtitlesFilter(EncodingJobInfo state, bool enableAlpha, bool enableSub2video)
 1817        {
 01818            var seconds = Math.Round(TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds);
 1819
 1820            // hls always copies timestamps
 01821            var setPtsParam = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive
 01822                ? string.Empty
 01823                : string.Format(CultureInfo.InvariantCulture, ",setpts=PTS -{0}/TB", seconds);
 1824
 01825            var alphaParam = enableAlpha ? ":alpha=1" : string.Empty;
 01826            var sub2videoParam = enableSub2video ? ":sub2video=1" : string.Empty;
 1827
 01828            var fontPath = _pathManager.GetAttachmentFolderPath(state.MediaSource.Id);
 01829            var fontParam = string.Format(
 01830                CultureInfo.InvariantCulture,
 01831                ":fontsdir='{0}'",
 01832                _mediaEncoder.EscapeSubtitleFilterPath(fontPath));
 1833
 01834            if (state.SubtitleStream.IsExternal)
 1835            {
 01836                var charsetParam = string.Empty;
 1837
 01838                if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
 1839                {
 01840                    var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
 01841                            state.SubtitleStream,
 01842                            state.SubtitleStream.Language,
 01843                            state.MediaSource,
 01844                            CancellationToken.None).GetAwaiter().GetResult();
 1845
 01846                    if (!string.IsNullOrEmpty(charenc))
 1847                    {
 01848                        charsetParam = ":charenc=" + charenc;
 1849                    }
 1850                }
 1851
 01852                return string.Format(
 01853                    CultureInfo.InvariantCulture,
 01854                    "subtitles=f='{0}'{1}{2}{3}{4}{5}",
 01855                    _mediaEncoder.EscapeSubtitleFilterPath(state.SubtitleStream.Path),
 01856                    charsetParam,
 01857                    alphaParam,
 01858                    sub2videoParam,
 01859                    fontParam,
 01860                    setPtsParam);
 1861            }
 1862
 01863            var subtitlePath = _subtitleEncoder.GetSubtitleFilePath(
 01864                    state.SubtitleStream,
 01865                    state.MediaSource,
 01866                    CancellationToken.None).GetAwaiter().GetResult();
 1867
 01868            return string.Format(
 01869                CultureInfo.InvariantCulture,
 01870                "subtitles=f='{0}'{1}{2}{3}{4}",
 01871                _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
 01872                alphaParam,
 01873                sub2videoParam,
 01874                fontParam,
 01875                setPtsParam);
 1876        }
 1877
 1878        public double? GetFramerateParam(EncodingJobInfo state)
 1879        {
 01880            var request = state.BaseRequest;
 1881
 01882            if (request.Framerate.HasValue)
 1883            {
 01884                return request.Framerate.Value;
 1885            }
 1886
 01887            var maxrate = request.MaxFramerate;
 1888
 01889            if (maxrate.HasValue && state.VideoStream is not null)
 1890            {
 01891                var contentRate = state.VideoStream.ReferenceFrameRate;
 1892
 01893                if (contentRate.HasValue && contentRate.Value > maxrate.Value)
 1894                {
 01895                    return maxrate;
 1896                }
 1897            }
 1898
 01899            return null;
 1900        }
 1901
 1902        public string GetHlsVideoKeyFrameArguments(
 1903            EncodingJobInfo state,
 1904            string codec,
 1905            int segmentLength,
 1906            bool isEventPlaylist,
 1907            int? startNumber)
 1908        {
 01909            var args = string.Empty;
 01910            var gopArg = string.Empty;
 1911
 01912            var keyFrameArg = string.Format(
 01913                CultureInfo.InvariantCulture,
 01914                " -force_key_frames:0 \"expr:gte(t,n_forced*{0})\"",
 01915                segmentLength);
 1916
 01917            var framerate = state.VideoStream?.RealFrameRate;
 01918            if (framerate.HasValue)
 1919            {
 1920                // This is to make sure keyframe interval is limited to our segment,
 1921                // as forcing keyframes is not enough.
 1922                // Example: we encoded half of desired length, then codec detected
 1923                // scene cut and inserted a keyframe; next forced keyframe would
 1924                // be created outside of segment, which breaks seeking.
 01925                gopArg = string.Format(
 01926                    CultureInfo.InvariantCulture,
 01927                    " -g:v:0 {0} -keyint_min:v:0 {0}",
 01928                    Math.Ceiling(segmentLength * framerate.Value));
 1929            }
 1930
 1931            // Unable to force key frames using these encoders, set key frames by GOP.
 01932            if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01933                || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 01934                || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01935                || string.Equals(codec, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 01936                || string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 01937                || string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 01938                || string.Equals(codec, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase)
 01939                || string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 01940                || string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 01941                || string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase)
 01942                || string.Equals(codec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1943            {
 01944                args += gopArg;
 1945            }
 01946            else if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase)
 01947                     || string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)
 01948                     || string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01949                     || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01950                     || string.Equals(codec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1951            {
 01952                args += keyFrameArg;
 1953
 1954                // prevent the libx264 from post processing to break the set keyframe.
 01955                if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase))
 1956                {
 01957                    args += " -sc_threshold:v:0 0";
 1958                }
 1959            }
 1960            else
 1961            {
 01962                args += keyFrameArg + gopArg;
 1963            }
 1964
 1965            // global_header produced by AMD HEVC VA-API encoder causes non-playable fMP4 on iOS
 01966            if (string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01967                && _mediaEncoder.IsVaapiDeviceAmd)
 1968            {
 01969                args += " -flags:v -global_header";
 1970            }
 1971
 01972            return args;
 1973        }
 1974
 1975        /// <summary>
 1976        /// Gets the video bitrate to specify on the command line.
 1977        /// </summary>
 1978        /// <param name="state">Encoding state.</param>
 1979        /// <param name="videoEncoder">Video encoder to use.</param>
 1980        /// <param name="encodingOptions">Encoding options.</param>
 1981        /// <param name="defaultPreset">Default present to use for encoding.</param>
 1982        /// <returns>Video bitrate.</returns>
 1983        public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, 
 1984        {
 01985            var param = string.Empty;
 1986
 1987            // Tutorials: Enable Intel GuC / HuC firmware loading for Low Power Encoding.
 1988            // https://01.org/group/43/downloads/firmware
 1989            // https://wiki.archlinux.org/title/intel_graphics#Enable_GuC_/_HuC_firmware_loading
 1990            // Intel Low Power Encoding can save unnecessary CPU-GPU synchronization,
 1991            // which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
 01992            var intelLowPowerHwEncoding = false;
 1993
 1994            // Workaround for linux 5.18 to 6.1.3 i915 hang at cost of performance.
 1995            // https://github.com/intel/media-driver/issues/1456
 01996            var enableWaFori915Hang = false;
 1997
 01998            var hardwareAccelerationType = encodingOptions.HardwareAccelerationType;
 1999
 02000            if (hardwareAccelerationType == HardwareAccelerationType.vaapi)
 2001            {
 02002                var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
 2003
 02004                if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
 2005                {
 02006                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder && isIntelVaapiDriver;
 2007                }
 02008                else if (string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
 2009                {
 02010                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder && isIntelVaapiDriver;
 2011                }
 2012            }
 02013            else if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 2014            {
 02015                if (OperatingSystem.IsLinux())
 2016                {
 02017                    var ver = Environment.OSVersion.Version;
 02018                    var isFixedKernel60 = ver.Major == 6 && ver.Minor == 0 && ver >= _minFixedKernel60i915Hang;
 02019                    var isUnaffectedKernel = ver < _minKerneli915Hang || ver > _maxKerneli915Hang;
 2020
 02021                    if (!(isUnaffectedKernel || isFixedKernel60))
 2022                    {
 02023                        var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
 02024                        var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
 02025                                             || vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 02026                        var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
 02027                            && IsVaapiSupported(state)
 02028                            && IsOpenclFullSupported()
 02029                            && !IsIntelVppTonemapAvailable(state, encodingOptions)
 02030                            && IsHwTonemapAvailable(state, encodingOptions);
 2031
 02032                        enableWaFori915Hang = isIntelDecoder && doOclTonemap;
 2033                    }
 2034                }
 2035
 02036                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 2037                {
 02038                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder;
 2039                }
 02040                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2041                {
 02042                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder;
 2043                }
 2044                else
 2045                {
 02046                    enableWaFori915Hang = false;
 2047                }
 2048            }
 2049
 02050            if (intelLowPowerHwEncoding)
 2051            {
 02052                param += " -low_power 1";
 2053            }
 2054
 02055            if (enableWaFori915Hang)
 2056            {
 02057                param += " -async_depth 1";
 2058            }
 2059
 02060            var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
 02061            var encodingPreset = encodingOptions.EncoderPreset;
 2062
 02063            param += GetEncoderParam(encodingPreset, defaultPreset, encodingOptions, videoEncoder, isLibX265);
 02064            param += GetVideoBitrateParam(state, videoEncoder);
 2065
 02066            var framerate = GetFramerateParam(state);
 02067            if (framerate.HasValue)
 2068            {
 02069                param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(CultureInfo.Inv
 2070            }
 2071
 02072            var targetVideoCodec = state.ActualOutputVideoCodec;
 02073            if (string.Equals(targetVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
 02074                || string.Equals(targetVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
 2075            {
 02076                targetVideoCodec = "hevc";
 2077            }
 2078
 02079            var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
 02080            profile = WhiteSpaceRegex().Replace(profile, string.Empty).ToLowerInvariant();
 2081
 02082            var videoProfiles = Array.Empty<string>();
 02083            if (string.Equals("h264", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2084            {
 02085                videoProfiles = _videoProfilesH264;
 2086            }
 02087            else if (string.Equals("hevc", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2088            {
 02089                videoProfiles = _videoProfilesH265;
 2090            }
 02091            else if (string.Equals("av1", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2092            {
 02093                videoProfiles = _videoProfilesAv1;
 2094            }
 2095
 02096            if (!videoProfiles.Contains(profile, StringComparison.OrdinalIgnoreCase))
 2097            {
 02098                profile = string.Empty;
 2099            }
 2100
 2101            // We only transcode to HEVC 8-bit for now, force Main Profile.
 02102            if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)
 02103                || profile.Contains("mainstill", StringComparison.OrdinalIgnoreCase))
 2104            {
 02105                profile = "main";
 2106            }
 2107
 2108            // Extended Profile is not supported by any known h264 encoders, force Main Profile.
 02109            if (profile.Contains("extended", StringComparison.OrdinalIgnoreCase))
 2110            {
 02111                profile = "main";
 2112            }
 2113
 2114            // Only libx264 support encoding H264 High 10 Profile, otherwise force High Profile.
 02115            if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02116                && profile.Contains("high10", StringComparison.OrdinalIgnoreCase))
 2117            {
 02118                profile = "high";
 2119            }
 2120
 2121            // We only need Main profile of AV1 encoders.
 02122            if (videoEncoder.Contains("av1", StringComparison.OrdinalIgnoreCase)
 02123                && (profile.Contains("high", StringComparison.OrdinalIgnoreCase)
 02124                    || profile.Contains("professional", StringComparison.OrdinalIgnoreCase)))
 2125            {
 02126                profile = "main";
 2127            }
 2128
 2129            // h264_vaapi does not support Baseline profile, force Constrained Baseline in this case,
 2130            // which is compatible (and ugly).
 02131            if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02132                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2133            {
 02134                profile = "constrained_baseline";
 2135            }
 2136
 2137            // libx264, h264_{qsv,nvenc,rkmpp} does not support Constrained Baseline profile, force Baseline in this cas
 02138            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02139                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02140                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02141                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02142                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2143            {
 02144                profile = "baseline";
 2145            }
 2146
 2147            // libx264, h264_{qsv,nvenc,vaapi,rkmpp} does not support Constrained High profile, force High in this case.
 02148            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02149                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02150                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02151                 || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02152                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02153                && profile.Contains("high", StringComparison.OrdinalIgnoreCase))
 2154            {
 02155                profile = "high";
 2156            }
 2157
 02158            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02159                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2160            {
 02161                profile = "constrained_baseline";
 2162            }
 2163
 02164            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02165                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2166            {
 02167                profile = "constrained_high";
 2168            }
 2169
 02170            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02171                && profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase))
 2172            {
 02173                profile = "constrained_baseline";
 2174            }
 2175
 02176            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02177                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2178            {
 02179                profile = "constrained_high";
 2180            }
 2181
 02182            if (!string.IsNullOrEmpty(profile))
 2183            {
 2184                // Currently there's no profile option in av1_nvenc encoder
 02185                if (!(string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 02186                      || string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)))
 2187                {
 02188                    param += " -profile:v:0 " + profile;
 2189                }
 2190            }
 2191
 02192            var level = state.GetRequestedLevel(targetVideoCodec);
 2193
 02194            if (!string.IsNullOrEmpty(level))
 2195            {
 02196                level = NormalizeTranscodingLevel(state, level);
 2197
 2198                // libx264, QSV, AMF can adjust the given level to match the output.
 02199                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02200                    || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2201                {
 02202                    param += " -level " + level;
 2203                }
 02204                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2205                {
 2206                    // hevc_qsv use -level 51 instead of -level 153.
 02207                    if (double.TryParse(level, CultureInfo.InvariantCulture, out double hevcLevel))
 2208                    {
 02209                        param += " -level " + (hevcLevel / 3);
 2210                    }
 2211                }
 02212                else if (string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 02213                         || string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 2214                {
 2215                    // libsvtav1 and av1_qsv use -level 60 instead of -level 16
 2216                    // https://aomedia.org/av1/specification/annex-a/
 02217                    if (int.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out int av1Level))
 2218                    {
 02219                        var x = 2 + (av1Level >> 2);
 02220                        var y = av1Level & 3;
 02221                        var res = (x * 10) + y;
 02222                        param += " -level " + res;
 2223                    }
 2224                }
 02225                else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02226                         || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 02227                         || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 2228                {
 02229                    param += " -level " + level;
 2230                }
 02231                else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02232                         || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 02233                         || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase))
 2234                {
 2235                    // level option may cause NVENC to fail.
 2236                    // NVENC cannot adjust the given level, just throw an error.
 2237                }
 02238                else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02239                         || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02240                         || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 2241                {
 2242                    // level option may cause corrupted frames on AMD VAAPI.
 02243                    if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 2244                    {
 02245                        param += " -level " + level;
 2246                    }
 2247                }
 02248                else if (string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 02249                         || string.Equals(videoEncoder, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase))
 2250                {
 02251                    param += " -level " + level;
 2252                }
 02253                else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2254                {
 02255                    param += " -level " + level;
 2256                }
 2257            }
 2258
 02259            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2260            {
 02261                param += " -x264opts:0 subme=0:me_range=16:rc_lookahead=10:me=hex:open_gop=0";
 2262            }
 2263
 02264            if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2265            {
 2266                // libx265 only accept level option in -x265-params.
 2267                // level option may cause libx265 to fail.
 2268                // libx265 cannot adjust the given level, just throw an error.
 02269                param += " -x265-params:0 no-scenecut=1:no-open-gop=1:no-info=1";
 2270
 02271                if (encodingOptions.EncoderPreset < EncoderPreset.ultrafast)
 2272                {
 2273                    // The following params are slower than the ultrafast preset, don't use when ultrafast is selected.
 02274                    param += ":subme=3:merange=25:rc-lookahead=10:me=star:ctu=32:max-tu-size=32:min-cu-size=16:rskip=2:r
 2275                }
 2276            }
 2277
 02278            if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)
 02279                && _mediaEncoder.EncoderVersion >= _minFFmpegSvtAv1Params)
 2280            {
 02281                param += " -svtav1-params:0 rc=1:tune=0:film-grain=0:enable-overlays=1:enable-tf=0";
 2282            }
 2283
 2284            /* Access unit too large: 8192 < 20880 error */
 02285            if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) ||
 02286                 string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) &&
 02287                 _mediaEncoder.EncoderVersion >= _minFFmpegVaapiH26xEncA53CcSei)
 2288            {
 02289                param += " -sei -a53_cc";
 2290            }
 2291
 02292            return param;
 2293        }
 2294
 2295        public bool CanStreamCopyVideo(EncodingJobInfo state, MediaStream videoStream)
 2296        {
 02297            var request = state.BaseRequest;
 2298
 02299            if (!request.AllowVideoStreamCopy)
 2300            {
 02301                return false;
 2302            }
 2303
 02304            if (videoStream.IsInterlaced
 02305                && state.DeInterlace(videoStream.Codec, false))
 2306            {
 02307                return false;
 2308            }
 2309
 02310            if (videoStream.IsAnamorphic ?? false)
 2311            {
 02312                if (request.RequireNonAnamorphic)
 2313                {
 02314                    return false;
 2315                }
 2316            }
 2317
 2318            // Can't stream copy if we're burning in subtitles
 02319            if (request.SubtitleStreamIndex.HasValue
 02320                && request.SubtitleStreamIndex.Value >= 0
 02321                && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
 2322            {
 02323                return false;
 2324            }
 2325
 02326            if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 02327                && videoStream.IsAVC.HasValue
 02328                && !videoStream.IsAVC.Value
 02329                && request.RequireAvc)
 2330            {
 02331                return false;
 2332            }
 2333
 2334            // Source and target codecs must match
 02335            if (string.IsNullOrEmpty(videoStream.Codec)
 02336                || (state.SupportedVideoCodecs.Length != 0
 02337                    && !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparison.OrdinalIgnoreCase)))
 2338            {
 02339                return false;
 2340            }
 2341
 02342            var requestedProfiles = state.GetRequestedProfiles(videoStream.Codec);
 2343
 2344            // If client is requesting a specific video profile, it must match the source
 02345            if (requestedProfiles.Length > 0)
 2346            {
 02347                if (string.IsNullOrEmpty(videoStream.Profile))
 2348                {
 2349                    // return false;
 2350                }
 2351
 02352                var requestedProfile = requestedProfiles[0];
 2353                // strip spaces because they may be stripped out on the query string as well
 02354                if (!string.IsNullOrEmpty(videoStream.Profile)
 02355                    && !requestedProfiles.Contains(videoStream.Profile.Replace(" ", string.Empty, StringComparison.Ordin
 2356                {
 02357                    var currentScore = GetVideoProfileScore(videoStream.Codec, videoStream.Profile);
 02358                    var requestedScore = GetVideoProfileScore(videoStream.Codec, requestedProfile);
 2359
 02360                    if (currentScore == -1 || currentScore > requestedScore)
 2361                    {
 02362                        return false;
 2363                    }
 2364                }
 2365            }
 2366
 02367            var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
 02368            if (requestedRangeTypes.Length > 0)
 2369            {
 02370                if (videoStream.VideoRangeType == VideoRangeType.Unknown)
 2371                {
 02372                    return false;
 2373                }
 2374
 2375                // DOVIWithHDR10 should be compatible with HDR10 supporting players. Same goes with HLG and of course SD
 02376                var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ord
 02377                var requestHasHLG = requestedRangeTypes.Contains(VideoRangeType.HLG.ToString(), StringComparison.Ordinal
 02378                var requestHasSDR = requestedRangeTypes.Contains(VideoRangeType.SDR.ToString(), StringComparison.Ordinal
 02379                var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.Ordin
 2380
 2381                // If SDR is the only supported range, we should not copy any of the HDR streams.
 2382                // All the following copy check assumes at least one HDR format is supported.
 02383                if (requestedRangeTypes.Length == 1 && requestHasSDR && videoStream.VideoRangeType != VideoRangeType.SDR
 2384                {
 02385                    return false;
 2386                }
 2387
 2388                // If the client does not support DOVI and the video stream is DOVI without fallback, we should not copy
 02389                if (!requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVI)
 2390                {
 02391                    return false;
 2392                }
 2393
 02394                if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreC
 02395                     && !((requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10)
 02396                            || (requestHasHLG && videoStream.VideoRangeType == VideoRangeType.DOVIWithHLG)
 02397                            || (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)
 02398                            || (requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.HDR10Plus)))
 2399                {
 2400                    // If the video stream is in HDR10+ or a static HDR format, don't allow copy if the client does not 
 02401                    if (videoStream.VideoRangeType is VideoRangeType.HDR10Plus or VideoRangeType.HDR10 or VideoRangeType
 2402                    {
 02403                        return false;
 2404                    }
 2405
 2406                    // Check complicated cases where we need to remove dynamic metadata
 2407                    // Conservatively refuse to copy if the encoder can't remove dynamic metadata,
 2408                    // but a removal is required for compatability reasons.
 02409                    var dynamicHdrMetadataRemovalPlan = ShouldRemoveDynamicHdrMetadata(state);
 02410                    if (!CanEncoderRemoveDynamicHdrMetadata(dynamicHdrMetadataRemovalPlan, videoStream))
 2411                    {
 02412                        return false;
 2413                    }
 2414                }
 2415            }
 2416
 2417            // Video width must fall within requested value
 02418            if (request.MaxWidth.HasValue
 02419                && (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
 2420            {
 02421                return false;
 2422            }
 2423
 2424            // Video height must fall within requested value
 02425            if (request.MaxHeight.HasValue
 02426                && (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value))
 2427            {
 02428                return false;
 2429            }
 2430
 2431            // Video framerate must fall within requested value
 02432            var requestedFramerate = request.MaxFramerate ?? request.Framerate;
 02433            if (requestedFramerate.HasValue)
 2434            {
 02435                var videoFrameRate = videoStream.ReferenceFrameRate;
 2436
 2437                // Add a little tolerance to the framerate check because some videos might record a framerate
 2438                // that is slightly greater than the intended framerate, but the device can still play it correctly.
 2439                // 0.05 fps tolerance should be safe enough.
 02440                if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value + 0.05f)
 2441                {
 02442                    return false;
 2443                }
 2444            }
 2445
 2446            // Video bitrate must fall within requested value
 02447            if (request.VideoBitRate.HasValue
 02448                && (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value))
 2449            {
 2450                // For LiveTV that has no bitrate, let's try copy if other conditions are met
 02451                if (string.IsNullOrWhiteSpace(request.LiveStreamId) || videoStream.BitRate.HasValue)
 2452                {
 02453                    return false;
 2454                }
 2455            }
 2456
 02457            var maxBitDepth = state.GetRequestedVideoBitDepth(videoStream.Codec);
 02458            if (maxBitDepth.HasValue)
 2459            {
 02460                if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > maxBitDepth.Value)
 2461                {
 02462                    return false;
 2463                }
 2464            }
 2465
 02466            var maxRefFrames = state.GetRequestedMaxRefFrames(videoStream.Codec);
 02467            if (maxRefFrames.HasValue
 02468                && videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > maxRefFrames.Value)
 2469            {
 02470                return false;
 2471            }
 2472
 2473            // If a specific level was requested, the source must match or be less than
 02474            var level = state.GetRequestedLevel(videoStream.Codec);
 02475            if (double.TryParse(level, CultureInfo.InvariantCulture, out var requestLevel))
 2476            {
 02477                if (!videoStream.Level.HasValue)
 2478                {
 2479                    // return false;
 2480                }
 2481
 02482                if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
 2483                {
 02484                    return false;
 2485                }
 2486            }
 2487
 02488            if (string.Equals(state.InputContainer, "avi", StringComparison.OrdinalIgnoreCase)
 02489                && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)
 02490                && !(videoStream.IsAVC ?? false))
 2491            {
 2492                // see Coach S01E01 - Kelly and the Professor(0).avi
 02493                return false;
 2494            }
 2495
 02496            return true;
 2497        }
 2498
 2499        public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, IEnumerable<string> supportedAudi
 2500        {
 02501            var request = state.BaseRequest;
 2502
 02503            if (!request.AllowAudioStreamCopy)
 2504            {
 02505                return false;
 2506            }
 2507
 02508            var maxBitDepth = state.GetRequestedAudioBitDepth(audioStream.Codec);
 02509            if (maxBitDepth.HasValue
 02510                && audioStream.BitDepth.HasValue
 02511                && audioStream.BitDepth.Value > maxBitDepth.Value)
 2512            {
 02513                return false;
 2514            }
 2515
 2516            // Source and target codecs must match
 02517            if (string.IsNullOrEmpty(audioStream.Codec)
 02518                || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparison.OrdinalIgnoreCase))
 2519            {
 02520                return false;
 2521            }
 2522
 2523            // Channels must fall within requested value
 02524            var channels = state.GetRequestedAudioChannels(audioStream.Codec);
 02525            if (channels.HasValue)
 2526            {
 02527                if (!audioStream.Channels.HasValue || audioStream.Channels.Value <= 0)
 2528                {
 02529                    return false;
 2530                }
 2531
 02532                if (audioStream.Channels.Value > channels.Value)
 2533                {
 02534                    return false;
 2535                }
 2536            }
 2537
 2538            // Sample rate must fall within requested value
 02539            if (request.AudioSampleRate.HasValue)
 2540            {
 02541                if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value <= 0)
 2542                {
 02543                    return false;
 2544                }
 2545
 02546                if (audioStream.SampleRate.Value > request.AudioSampleRate.Value)
 2547                {
 02548                    return false;
 2549                }
 2550            }
 2551
 2552            // Audio bitrate must fall within requested value
 02553            if (request.AudioBitRate.HasValue
 02554                && audioStream.BitRate.HasValue
 02555                && audioStream.BitRate.Value > request.AudioBitRate.Value)
 2556            {
 02557                return false;
 2558            }
 2559
 02560            return request.EnableAutoStreamCopy;
 2561        }
 2562
 2563        public int GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideo
 2564        {
 02565            var bitrate = request.VideoBitRate;
 2566
 02567            if (videoStream is not null)
 2568            {
 02569                var isUpscaling = request.Height.HasValue
 02570                    && videoStream.Height.HasValue
 02571                    && request.Height.Value > videoStream.Height.Value
 02572                    && request.Width.HasValue
 02573                    && videoStream.Width.HasValue
 02574                    && request.Width.Value > videoStream.Width.Value;
 2575
 2576                // Don't allow bitrate increases unless upscaling
 02577                if (!isUpscaling && bitrate.HasValue && videoStream.BitRate.HasValue)
 2578                {
 02579                    bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value);
 2580                }
 2581
 02582                if (bitrate.HasValue)
 2583                {
 02584                    var inputVideoCodec = videoStream.Codec;
 02585                    bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
 2586
 2587                    // If a max bitrate was requested, don't let the scaled bitrate exceed it
 02588                    if (request.VideoBitRate.HasValue)
 2589                    {
 02590                        bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
 2591                    }
 2592                }
 2593            }
 2594
 2595            // Cap the max target bitrate to intMax/2 to satisfy the bufsize=bitrate*2.
 02596            return Math.Min(bitrate ?? 0, int.MaxValue / 2);
 2597        }
 2598
 2599        private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
 2600        {
 2601            // these values were chosen from testing to improve low bitrate streams
 02602            if (sourceBitrate <= 2000000)
 2603            {
 02604                sourceBitrate = Convert.ToInt32(sourceBitrate * 2.5);
 2605            }
 02606            else if (sourceBitrate <= 3000000)
 2607            {
 02608                sourceBitrate *= 2;
 2609            }
 2610
 02611            var bitrate = Math.Min(sourceBitrate, requestedBitrate);
 2612
 02613            return bitrate;
 2614        }
 2615
 2616        private static double GetVideoBitrateScaleFactor(string codec)
 2617        {
 2618            // hevc & vp9 - 40% more efficient than h.264
 02619            if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 02620                || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
 02621                || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
 2622            {
 02623                return .6;
 2624            }
 2625
 2626            // av1 - 50% more efficient than h.264
 02627            if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 2628            {
 02629                return .5;
 2630            }
 2631
 02632            return 1;
 2633        }
 2634
 2635        public static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
 2636        {
 02637            var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
 02638            var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
 2639
 2640            // Don't scale the real bitrate lower than the requested bitrate
 02641            var scaleFactor = Math.Max(outputScaleFactor / inputScaleFactor, 1);
 2642
 02643            if (bitrate <= 500000)
 2644            {
 02645                scaleFactor = Math.Max(scaleFactor, 4);
 2646            }
 02647            else if (bitrate <= 1000000)
 2648            {
 02649                scaleFactor = Math.Max(scaleFactor, 3);
 2650            }
 02651            else if (bitrate <= 2000000)
 2652            {
 02653                scaleFactor = Math.Max(scaleFactor, 2.5);
 2654            }
 02655            else if (bitrate <= 3000000)
 2656            {
 02657                scaleFactor = Math.Max(scaleFactor, 2);
 2658            }
 02659            else if (bitrate >= 30000000)
 2660            {
 2661                // Don't scale beyond 30Mbps, it is hardly visually noticeable for most codecs with our prefer speed enc
 2662                // and will cause extremely high bitrate to be used for av1->h264 transcoding that will overload clients
 02663                scaleFactor = 1;
 2664            }
 2665
 02666            return Convert.ToInt32(scaleFactor * bitrate);
 2667        }
 2668
 2669        public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChanne
 2670        {
 02671            return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels);
 2672        }
 2673
 2674        public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudio
 2675        {
 02676            if (audioStream is null)
 2677            {
 02678                return null;
 2679            }
 2680
 02681            var inputChannels = audioStream.Channels ?? 0;
 02682            var outputChannels = outputAudioChannels ?? 0;
 02683            var bitrate = audioBitRate ?? int.MaxValue;
 2684
 02685            if (string.IsNullOrEmpty(audioCodec)
 02686                || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
 02687                || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
 02688                || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
 02689                || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
 02690                || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
 02691                || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
 2692            {
 02693                return (inputChannels, outputChannels) switch
 02694                {
 02695                    (>= 6, >= 6 or 0) => Math.Min(640000, bitrate),
 02696                    (> 0, > 0) => Math.Min(outputChannels * 128000, bitrate),
 02697                    (> 0, _) => Math.Min(inputChannels * 128000, bitrate),
 02698                    (_, _) => Math.Min(384000, bitrate)
 02699                };
 2700            }
 2701
 02702            if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
 02703                || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
 2704            {
 02705                return (inputChannels, outputChannels) switch
 02706                {
 02707                    (>= 6, >= 6 or 0) => Math.Min(768000, bitrate),
 02708                    (> 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
 02709                    (> 0, _) => Math.Min(inputChannels * 136000, bitrate),
 02710                    (_, _) => Math.Min(672000, bitrate)
 02711                };
 2712            }
 2713
 2714            // Empty bitrate area is not allow on iOS
 2715            // Default audio bitrate to 128K per channel if we don't have codec specific defaults
 2716            // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
 02717            return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
 2718        }
 2719
 2720        public string GetAudioVbrModeParam(string encoder, int bitrate, int channels)
 2721        {
 02722            var bitratePerChannel = bitrate / Math.Max(channels, 1);
 02723            if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
 2724            {
 02725                return " -vbr:a " + bitratePerChannel switch
 02726                {
 02727                    < 32000 => "1",
 02728                    < 48000 => "2",
 02729                    < 64000 => "3",
 02730                    < 96000 => "4",
 02731                    _ => "5"
 02732                };
 2733            }
 2734
 02735            if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase))
 2736            {
 2737                // lame's VBR is only good for a certain bitrate range
 2738                // For very low and very high bitrate, use abr mode
 02739                if (bitratePerChannel is < 122500 and > 48000)
 2740                {
 02741                    return " -qscale:a " + bitratePerChannel switch
 02742                    {
 02743                        < 64000 => "6",
 02744                        < 88000 => "4",
 02745                        < 112000 => "2",
 02746                        _ => "0"
 02747                    };
 2748                }
 2749
 02750                return " -abr:a 1" + " -b:a " + bitrate;
 2751            }
 2752
 02753            if (string.Equals(encoder, "aac_at", StringComparison.OrdinalIgnoreCase))
 2754            {
 2755                // aac_at's CVBR mode
 02756                return " -aac_at_mode:a 2" + " -b:a " + bitrate;
 2757            }
 2758
 02759            if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase))
 2760            {
 02761                return " -qscale:a " + bitratePerChannel switch
 02762                {
 02763                    < 40000 => "0",
 02764                    < 56000 => "2",
 02765                    < 80000 => "4",
 02766                    < 112000 => "6",
 02767                    _ => "8"
 02768                };
 2769            }
 2770
 02771            return null;
 2772        }
 2773
 2774        public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
 2775        {
 02776            var channels = state.OutputAudioChannels;
 2777
 02778            var filters = new List<string>();
 2779
 02780            if (channels is 2 && state.AudioStream?.Channels is > 2)
 2781            {
 02782                var hasDownMixFilter = DownMixAlgorithmsHelper.AlgorithmFilterStrings.TryGetValue((encodingOptions.DownM
 02783                if (hasDownMixFilter)
 2784                {
 02785                    filters.Add(downMixFilterString);
 2786                }
 2787
 02788                if (!encodingOptions.DownMixAudioBoost.Equals(1))
 2789                {
 02790                    filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
 2791                }
 2792            }
 2793
 02794            var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
 02795            if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && ShouldEncodeSubtitle(st
 2796            {
 02797                var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds;
 2798
 02799                filters.Add(
 02800                    string.Format(
 02801                        CultureInfo.InvariantCulture,
 02802                        "asetpts=PTS-{0}/TB",
 02803                        Math.Round(seconds)));
 2804            }
 2805
 02806            if (filters.Count > 0)
 2807            {
 02808                return " -af \"" + string.Join(',', filters) + "\"";
 2809            }
 2810
 02811            return string.Empty;
 2812        }
 2813
 2814        /// <summary>
 2815        /// Gets the number of audio channels to specify on the command line.
 2816        /// </summary>
 2817        /// <param name="state">The state.</param>
 2818        /// <param name="audioStream">The audio stream.</param>
 2819        /// <param name="outputAudioCodec">The output audio codec.</param>
 2820        /// <returns>System.Nullable{System.Int32}.</returns>
 2821        public int? GetNumAudioChannelsParam(EncodingJobInfo state, MediaStream audioStream, string outputAudioCodec)
 2822        {
 02823            if (audioStream is null)
 2824            {
 02825                return null;
 2826            }
 2827
 02828            var request = state.BaseRequest;
 2829
 02830            var codec = outputAudioCodec ?? string.Empty;
 2831
 02832            int? resultChannels = state.GetRequestedAudioChannels(codec);
 2833
 02834            var inputChannels = audioStream.Channels;
 2835
 02836            if (inputChannels > 0)
 2837            {
 02838                resultChannels = inputChannels < resultChannels ? inputChannels : resultChannels ?? inputChannels;
 2839            }
 2840
 02841            var isTranscodingAudio = !IsCopyCodec(codec);
 2842
 02843            if (isTranscodingAudio)
 2844            {
 02845                var audioEncoder = GetAudioEncoder(state);
 02846                if (!_audioTranscodeChannelLookup.TryGetValue(audioEncoder, out var transcoderChannelLimit))
 2847                {
 2848                    // Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many c
 02849                    transcoderChannelLimit = 8;
 2850                }
 2851
 2852                // Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelL
 02853                resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? tr
 2854
 02855                if (request.TranscodingMaxAudioChannels < resultChannels)
 2856                {
 02857                    resultChannels = request.TranscodingMaxAudioChannels;
 2858                }
 2859
 2860                // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
 2861                // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_d
 02862                if (state.TranscodingType != TranscodingJobType.Progressive
 02863                    && ((resultChannels > 2 && resultChannels < 6) || resultChannels == 7))
 2864                {
 2865                    // We can let FFMpeg supply an extra LFE channel for 5ch and 7ch to make them 5.1 and 7.1
 02866                    if (resultChannels == 5)
 2867                    {
 02868                        resultChannels = 6;
 2869                    }
 02870                    else if (resultChannels == 7)
 2871                    {
 02872                        resultChannels = 8;
 2873                    }
 2874                    else
 2875                    {
 2876                        // For other weird layout, just downmix to stereo for compatibility
 02877                        resultChannels = 2;
 2878                    }
 2879                }
 2880            }
 2881
 02882            return resultChannels;
 2883        }
 2884
 2885        /// <summary>
 2886        /// Enforces the resolution limit.
 2887        /// </summary>
 2888        /// <param name="state">The state.</param>
 2889        public void EnforceResolutionLimit(EncodingJobInfo state)
 2890        {
 02891            var videoRequest = state.BaseRequest;
 2892
 2893            // Switch the incoming params to be ceilings rather than fixed values
 02894            videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
 02895            videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
 2896
 02897            videoRequest.Width = null;
 02898            videoRequest.Height = null;
 02899        }
 2900
 2901        /// <summary>
 2902        /// Gets the fast seek command line parameter.
 2903        /// </summary>
 2904        /// <param name="state">The state.</param>
 2905        /// <param name="options">The options.</param>
 2906        /// <param name="segmentContainer">Segment Container.</param>
 2907        /// <returns>System.String.</returns>
 2908        /// <value>The fast seek command line parameter.</value>
 2909        public string GetFastSeekCommandLineParameter(EncodingJobInfo state, EncodingOptions options, string segmentCont
 2910        {
 02911            var time = state.BaseRequest.StartTimeTicks ?? 0;
 02912            var maxTime = state.RunTimeTicks ?? 0;
 02913            var seekParam = string.Empty;
 2914
 02915            if (time > 0)
 2916            {
 2917                // For direct streaming/remuxing, HLS segments start at keyframes.
 2918                // However, ffmpeg will seek to previous keyframe when the exact frame time is the input
 2919                // Workaround this by adding 0.5s offset to the seeking time to get the exact keyframe on most videos.
 2920                // This will help subtitle syncing.
 02921                var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCod
 02922                var seekTick = isHlsRemuxing ? time + 5000000L : time;
 2923
 2924                // Seeking beyond EOF makes no sense in transcoding. Clamp the seekTick value to
 2925                // [0, RuntimeTicks - 5.0s], so that the muxer gets packets and avoid error codes.
 02926                if (maxTime > 0)
 2927                {
 02928                    seekTick = Math.Clamp(seekTick, 0, Math.Max(maxTime - 50000000L, 0));
 2929                }
 2930
 02931                seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(seekT
 2932
 02933                if (state.IsVideoRequest)
 2934                {
 2935                    // If we are remuxing, then the copied stream cannot be seeked accurately (it will seek to the neare
 2936                    // keyframe). If we are using fMP4, then force all other streams to use the same inaccurate seeking 
 2937                    // avoid A/V sync issues which cause playback issues on some devices.
 2938                    // When remuxing video, the segment start times correspond to key frames in the source stream, so th
 2939                    // option shouldn't change the seeked point that much.
 2940                    // Important: make sure not to use it with wtv because it breaks seeking
 02941                    if (state.TranscodingType is TranscodingJobType.Hls
 02942                        && string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase)
 02943                        && (IsCopyCodec(state.OutputVideoCodec) || IsCopyCodec(state.OutputAudioCodec))
 02944                        && !string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase))
 2945                    {
 02946                        seekParam += " -noaccurate_seek";
 2947                    }
 2948                }
 2949            }
 2950
 02951            return seekParam;
 2952        }
 2953
 2954        /// <summary>
 2955        /// Gets the map args.
 2956        /// </summary>
 2957        /// <param name="state">The state.</param>
 2958        /// <returns>System.String.</returns>
 2959        public string GetMapArgs(EncodingJobInfo state)
 2960        {
 2961            // If we don't have known media info
 2962            // If input is video, use -sn to drop subtitles
 2963            // Otherwise just return empty
 02964            if (state.VideoStream is null && state.AudioStream is null)
 2965            {
 02966                return state.IsInputVideo ? "-sn" : string.Empty;
 2967            }
 2968
 2969            // We have media info, but we don't know the stream index
 02970            if (state.VideoStream is not null && state.VideoStream.Index == -1)
 2971            {
 02972                return "-sn";
 2973            }
 2974
 2975            // We have media info, but we don't know the stream index
 02976            if (state.AudioStream is not null && state.AudioStream.Index == -1)
 2977            {
 02978                return state.IsInputVideo ? "-sn" : string.Empty;
 2979            }
 2980
 02981            var args = string.Empty;
 2982
 02983            if (state.VideoStream is not null)
 2984            {
 02985                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 2986
 02987                args += string.Format(
 02988                    CultureInfo.InvariantCulture,
 02989                    "-map 0:{0}",
 02990                    videoStreamIndex);
 2991            }
 2992            else
 2993            {
 2994                // No known video stream
 02995                args += "-vn";
 2996            }
 2997
 02998            if (state.AudioStream is not null)
 2999            {
 03000                int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
 03001                if (state.AudioStream.IsExternal)
 3002                {
 03003                    bool hasExternalGraphicsSubs = state.SubtitleStream is not null
 03004                        && ShouldEncodeSubtitle(state)
 03005                        && state.SubtitleStream.IsExternal
 03006                        && !state.SubtitleStream.IsTextSubtitleStream;
 03007                    int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
 3008
 03009                    args += string.Format(
 03010                        CultureInfo.InvariantCulture,
 03011                        " -map {0}:{1}",
 03012                        externalAudioMapIndex,
 03013                        audioStreamIndex);
 3014                }
 3015                else
 3016                {
 03017                    args += string.Format(
 03018                        CultureInfo.InvariantCulture,
 03019                        " -map 0:{0}",
 03020                        audioStreamIndex);
 3021                }
 3022            }
 3023            else
 3024            {
 03025                args += " -map -0:a";
 3026            }
 3027
 03028            var subtitleMethod = state.SubtitleDeliveryMethod;
 03029            if (state.SubtitleStream is null || subtitleMethod == SubtitleDeliveryMethod.Hls)
 3030            {
 03031                args += " -map -0:s";
 3032            }
 03033            else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
 3034            {
 03035                int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3036
 03037                args += string.Format(
 03038                    CultureInfo.InvariantCulture,
 03039                    " -map 0:{0}",
 03040                    subtitleStreamIndex);
 3041            }
 03042            else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
 3043            {
 03044                int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3045
 03046                args += string.Format(
 03047                    CultureInfo.InvariantCulture,
 03048                    " -map 1:{0} -sn",
 03049                    externalSubtitleStreamIndex);
 3050            }
 3051
 03052            return args;
 3053        }
 3054
 3055        /// <summary>
 3056        /// Gets the negative map args by filters.
 3057        /// </summary>
 3058        /// <param name="state">The state.</param>
 3059        /// <param name="videoProcessFilters">The videoProcessFilters.</param>
 3060        /// <returns>System.String.</returns>
 3061        public string GetNegativeMapArgsByFilters(EncodingJobInfo state, string videoProcessFilters)
 3062        {
 03063            string args = string.Empty;
 3064
 3065            // http://ffmpeg.org/ffmpeg-all.html#toc-Complex-filtergraphs-1
 03066            if (state.VideoStream is not null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordina
 3067            {
 03068                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 3069
 03070                args += string.Format(
 03071                    CultureInfo.InvariantCulture,
 03072                    "-map -0:{0} ",
 03073                    videoStreamIndex);
 3074            }
 3075
 03076            return args;
 3077        }
 3078
 3079        /// <summary>
 3080        /// Determines which stream will be used for playback.
 3081        /// </summary>
 3082        /// <param name="allStream">All stream.</param>
 3083        /// <param name="desiredIndex">Index of the desired.</param>
 3084        /// <param name="type">The type.</param>
 3085        /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
 3086        /// <returns>MediaStream.</returns>
 3087        public MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, b
 3088        {
 03089            var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
 3090
 03091            if (desiredIndex.HasValue)
 3092            {
 03093                var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
 3094
 03095                if (stream is not null)
 3096                {
 03097                    return stream;
 3098                }
 3099            }
 3100
 03101            if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
 3102            {
 03103                return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
 03104                       streams.FirstOrDefault();
 3105            }
 3106
 3107            // Just return the first one
 03108            return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
 3109        }
 3110
 3111        public static (int? Width, int? Height) GetFixedOutputSize(
 3112            int? videoWidth,
 3113            int? videoHeight,
 3114            int? requestedWidth,
 3115            int? requestedHeight,
 3116            int? requestedMaxWidth,
 3117            int? requestedMaxHeight)
 3118        {
 03119            if (!videoWidth.HasValue && !requestedWidth.HasValue)
 3120            {
 03121                return (null, null);
 3122            }
 3123
 03124            if (!videoHeight.HasValue && !requestedHeight.HasValue)
 3125            {
 03126                return (null, null);
 3127            }
 3128
 03129            int inputWidth = Convert.ToInt32(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture);
 03130            int inputHeight = Convert.ToInt32(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture);
 03131            int outputWidth = requestedWidth ?? inputWidth;
 03132            int outputHeight = requestedHeight ?? inputHeight;
 3133
 3134            // Don't transcode video to bigger than 4k when using HW.
 03135            int maximumWidth = Math.Min(requestedMaxWidth ?? outputWidth, 4096);
 03136            int maximumHeight = Math.Min(requestedMaxHeight ?? outputHeight, 4096);
 3137
 03138            if (outputWidth > maximumWidth || outputHeight > maximumHeight)
 3139            {
 03140                var scaleW = (double)maximumWidth / outputWidth;
 03141                var scaleH = (double)maximumHeight / outputHeight;
 03142                var scale = Math.Min(scaleW, scaleH);
 03143                outputWidth = Math.Min(maximumWidth, Convert.ToInt32(outputWidth * scale));
 03144                outputHeight = Math.Min(maximumHeight, Convert.ToInt32(outputHeight * scale));
 3145            }
 3146
 03147            outputWidth = 2 * (outputWidth / 2);
 03148            outputHeight = 2 * (outputHeight / 2);
 3149
 03150            return (outputWidth, outputHeight);
 3151        }
 3152
 3153        public static bool IsScaleRatioSupported(
 3154            int? videoWidth,
 3155            int? videoHeight,
 3156            int? requestedWidth,
 3157            int? requestedHeight,
 3158            int? requestedMaxWidth,
 3159            int? requestedMaxHeight,
 3160            double? maxScaleRatio)
 3161        {
 03162            var (outWidth, outHeight) = GetFixedOutputSize(
 03163                videoWidth,
 03164                videoHeight,
 03165                requestedWidth,
 03166                requestedHeight,
 03167                requestedMaxWidth,
 03168                requestedMaxHeight);
 3169
 03170            if (!videoWidth.HasValue
 03171                 || !videoHeight.HasValue
 03172                 || !outWidth.HasValue
 03173                 || !outHeight.HasValue
 03174                 || !maxScaleRatio.HasValue
 03175                 || (maxScaleRatio.Value < 1.0f))
 3176            {
 03177                return false;
 3178            }
 3179
 03180            var minScaleRatio = 1.0f / maxScaleRatio;
 03181            var scaleRatioW = (double)outWidth / (double)videoWidth;
 03182            var scaleRatioH = (double)outHeight / (double)videoHeight;
 3183
 03184            if (scaleRatioW < minScaleRatio
 03185                || scaleRatioW > maxScaleRatio
 03186                || scaleRatioH < minScaleRatio
 03187                || scaleRatioH > maxScaleRatio)
 3188            {
 03189                return false;
 3190            }
 3191
 03192            return true;
 3193        }
 3194
 3195        public static string GetHwScaleFilter(
 3196            string hwScalePrefix,
 3197            string hwScaleSuffix,
 3198            string videoFormat,
 3199            bool swapOutputWandH,
 3200            int? videoWidth,
 3201            int? videoHeight,
 3202            int? requestedWidth,
 3203            int? requestedHeight,
 3204            int? requestedMaxWidth,
 3205            int? requestedMaxHeight)
 3206        {
 03207            var (outWidth, outHeight) = GetFixedOutputSize(
 03208                videoWidth,
 03209                videoHeight,
 03210                requestedWidth,
 03211                requestedHeight,
 03212                requestedMaxWidth,
 03213                requestedMaxHeight);
 3214
 03215            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03216            var isSizeFixed = !videoWidth.HasValue
 03217                || outWidth.Value != videoWidth.Value
 03218                || !videoHeight.HasValue
 03219                || outHeight.Value != videoHeight.Value;
 3220
 03221            var swpOutW = swapOutputWandH ? outHeight.Value : outWidth.Value;
 03222            var swpOutH = swapOutputWandH ? outWidth.Value : outHeight.Value;
 3223
 03224            var arg1 = isSizeFixed ? $"=w={swpOutW}:h={swpOutH}" : string.Empty;
 03225            var arg2 = isFormatFixed ? $"format={videoFormat}" : string.Empty;
 03226            if (isFormatFixed)
 3227            {
 03228                arg2 = (isSizeFixed ? ':' : '=') + arg2;
 3229            }
 3230
 03231            if (!string.IsNullOrEmpty(hwScaleSuffix) && (isSizeFixed || isFormatFixed))
 3232            {
 03233                return string.Format(
 03234                    CultureInfo.InvariantCulture,
 03235                    "{0}_{1}{2}{3}",
 03236                    hwScalePrefix ?? "scale",
 03237                    hwScaleSuffix,
 03238                    arg1,
 03239                    arg2);
 3240            }
 3241
 03242            return string.Empty;
 3243        }
 3244
 3245        public static string GetGraphicalSubPreProcessFilters(
 3246            int? videoWidth,
 3247            int? videoHeight,
 3248            int? subtitleWidth,
 3249            int? subtitleHeight,
 3250            int? requestedWidth,
 3251            int? requestedHeight,
 3252            int? requestedMaxWidth,
 3253            int? requestedMaxHeight)
 3254        {
 03255            var (outWidth, outHeight) = GetFixedOutputSize(
 03256                videoWidth,
 03257                videoHeight,
 03258                requestedWidth,
 03259                requestedHeight,
 03260                requestedMaxWidth,
 03261                requestedMaxHeight);
 3262
 03263            if (!outWidth.HasValue
 03264                || !outHeight.HasValue
 03265                || outWidth.Value <= 0
 03266                || outHeight.Value <= 0)
 3267            {
 03268                return string.Empty;
 3269            }
 3270
 3271            // Automatically add padding based on subtitle input
 03272            var filters = @"scale,scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:blac
 3273
 03274            if (subtitleWidth.HasValue
 03275                && subtitleHeight.HasValue
 03276                && subtitleWidth.Value > 0
 03277                && subtitleHeight.Value > 0)
 3278            {
 03279                var videoDar = (double)outWidth.Value / outHeight.Value;
 03280                var subtitleDar = (double)subtitleWidth.Value / subtitleHeight.Value;
 3281
 3282                // No need to add padding when DAR is the same -> 1080p PGSSUB on 2160p video
 03283                if (Math.Abs(videoDar - subtitleDar) < 0.01f)
 3284                {
 03285                    filters = @"scale,scale={0}:{1}:fast_bilinear";
 3286                }
 3287            }
 3288
 03289            return string.Format(
 03290                CultureInfo.InvariantCulture,
 03291                filters,
 03292                outWidth.Value,
 03293                outHeight.Value);
 3294        }
 3295
 3296        public static string GetAlphaSrcFilter(
 3297            EncodingJobInfo state,
 3298            int? videoWidth,
 3299            int? videoHeight,
 3300            int? requestedWidth,
 3301            int? requestedHeight,
 3302            int? requestedMaxWidth,
 3303            int? requestedMaxHeight,
 3304            float? framerate)
 3305        {
 03306            var reqTicks = state.BaseRequest.StartTimeTicks ?? 0;
 03307            var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture
 03308            var (outWidth, outHeight) = GetFixedOutputSize(
 03309                videoWidth,
 03310                videoHeight,
 03311                requestedWidth,
 03312                requestedHeight,
 03313                requestedMaxWidth,
 03314                requestedMaxHeight);
 3315
 03316            if (outWidth.HasValue && outHeight.HasValue)
 3317            {
 03318                return string.Format(
 03319                    CultureInfo.InvariantCulture,
 03320                    "alphasrc=s={0}x{1}:r={2}:start='{3}'",
 03321                    outWidth.Value,
 03322                    outHeight.Value,
 03323                    framerate ?? 25,
 03324                    reqTicks > 0 ? startTime : 0);
 3325            }
 3326
 03327            return string.Empty;
 3328        }
 3329
 3330        public static string GetSwScaleFilter(
 3331            EncodingJobInfo state,
 3332            EncodingOptions options,
 3333            string videoEncoder,
 3334            int? videoWidth,
 3335            int? videoHeight,
 3336            Video3DFormat? threedFormat,
 3337            int? requestedWidth,
 3338            int? requestedHeight,
 3339            int? requestedMaxWidth,
 3340            int? requestedMaxHeight)
 3341        {
 03342            var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 03343            var isMjpeg = videoEncoder is not null && videoEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase)
 03344            var scaleVal = isV4l2 ? 64 : 2;
 03345            var targetAr = isMjpeg ? "(a*sar)" : "a"; // manually calculate AR when using mjpeg encoder
 3346
 3347            // If fixed dimensions were supplied
 03348            if (requestedWidth.HasValue && requestedHeight.HasValue)
 3349            {
 03350                if (isV4l2)
 3351                {
 03352                    var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 03353                    var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3354
 03355                    return string.Format(
 03356                            CultureInfo.InvariantCulture,
 03357                            "scale=trunc({0}/64)*64:trunc({1}/2)*2",
 03358                            widthParam,
 03359                            heightParam);
 3360                }
 3361
 03362                return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
 3363            }
 3364
 3365            // If Max dimensions were supplied, for width selects lowest even number between input width and width req s
 3366
 03367            if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
 3368            {
 03369                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 03370                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3371
 03372                return string.Format(
 03373                    CultureInfo.InvariantCulture,
 03374                    @"scale=trunc(min(max(iw\,ih*{3})\,min({0}\,{1}*{3}))/{2})*{2}:trunc(min(max(iw/{3}\,ih)\,min({0}/{3
 03375                    maxWidthParam,
 03376                    maxHeightParam,
 03377                    scaleVal,
 03378                    targetAr);
 3379            }
 3380
 3381            // If a fixed width was requested
 03382            if (requestedWidth.HasValue)
 3383            {
 03384                if (threedFormat.HasValue)
 3385                {
 3386                    // This method can handle 0 being passed in for the requested height
 03387                    return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0);
 3388                }
 3389
 03390                var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 3391
 03392                return string.Format(
 03393                    CultureInfo.InvariantCulture,
 03394                    "scale={0}:trunc(ow/{1}/2)*2",
 03395                    widthParam,
 03396                    targetAr);
 3397            }
 3398
 3399            // If a fixed height was requested
 03400            if (requestedHeight.HasValue)
 3401            {
 03402                var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3403
 03404                return string.Format(
 03405                    CultureInfo.InvariantCulture,
 03406                    "scale=trunc(oh*{2}/{1})*{1}:{0}",
 03407                    heightParam,
 03408                    scaleVal,
 03409                    targetAr);
 3410            }
 3411
 3412            // If a max width was requested
 03413            if (requestedMaxWidth.HasValue)
 3414            {
 03415                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 3416
 03417                return string.Format(
 03418                    CultureInfo.InvariantCulture,
 03419                    @"scale=trunc(min(max(iw\,ih*{2})\,{0})/{1})*{1}:trunc(ow/{2}/2)*2",
 03420                    maxWidthParam,
 03421                    scaleVal,
 03422                    targetAr);
 3423            }
 3424
 3425            // If a max height was requested
 03426            if (requestedMaxHeight.HasValue)
 3427            {
 03428                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3429
 03430                return string.Format(
 03431                    CultureInfo.InvariantCulture,
 03432                    @"scale=trunc(oh*{2}/{1})*{1}:min(max(iw/{2}\,ih)\,{0})",
 03433                    maxHeightParam,
 03434                    scaleVal,
 03435                    targetAr);
 3436            }
 3437
 03438            return string.Empty;
 3439        }
 3440
 3441        private static string GetFixedSwScaleFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight
 3442        {
 03443            var widthParam = requestedWidth.ToString(CultureInfo.InvariantCulture);
 03444            var heightParam = requestedHeight.ToString(CultureInfo.InvariantCulture);
 3445
 03446            string filter = null;
 3447
 03448            if (threedFormat.HasValue)
 3449            {
 03450                switch (threedFormat.Value)
 3451                {
 3452                    case Video3DFormat.HalfSideBySide:
 03453                        filter = @"crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(i
 3454                        // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars
 03455                        break;
 3456                    case Video3DFormat.FullSideBySide:
 03457                        filter = @"crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3458                        // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the s
 03459                        break;
 3460                    case Video3DFormat.HalfTopAndBottom:
 03461                        filter = @"crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(
 3462                        // htab crop height in half,scale to correct size, set the display aspect,crop out any black bar
 03463                        break;
 3464                    case Video3DFormat.FullTopAndBottom:
 03465                        filter = @"crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3466                        // ftab crop height in half, set the display aspect,crop out any black bars we may have made the
 3467                        break;
 3468                    default:
 3469                        break;
 3470                }
 3471            }
 3472
 3473            // default
 03474            if (filter is null)
 3475            {
 03476                if (requestedHeight > 0)
 3477                {
 03478                    filter = "scale=trunc({0}/2)*2:trunc({1}/2)*2";
 3479                }
 3480                else
 3481                {
 03482                    filter = "scale={0}:trunc({0}/a/2)*2";
 3483                }
 3484            }
 3485
 03486            return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam);
 3487        }
 3488
 3489        public static string GetSwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options)
 3490        {
 03491            var doubleRateDeint = options.DeinterlaceDoubleRate && state.VideoStream?.ReferenceFrameRate <= 30;
 03492            return string.Format(
 03493                CultureInfo.InvariantCulture,
 03494                "{0}={1}:-1:0",
 03495                options.DeinterlaceMethod.ToString().ToLowerInvariant(),
 03496                doubleRateDeint ? "1" : "0");
 3497        }
 3498
 3499        public string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix)
 3500        {
 03501            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03502            if (hwDeintSuffix.Contains("cuda", StringComparison.OrdinalIgnoreCase))
 3503            {
 03504                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3505
 03506                return string.Format(
 03507                    CultureInfo.InvariantCulture,
 03508                    "{0}_cuda={1}:-1:0",
 03509                    useBwdif ? "bwdif" : "yadif",
 03510                    doubleRateDeint ? "1" : "0");
 3511            }
 3512
 03513            if (hwDeintSuffix.Contains("opencl", StringComparison.OrdinalIgnoreCase))
 3514            {
 03515                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif;
 3516
 03517                if (_mediaEncoder.SupportsFilter("yadif_opencl")
 03518                    && _mediaEncoder.SupportsFilter("bwdif_opencl"))
 3519                {
 03520                    return string.Format(
 03521                        CultureInfo.InvariantCulture,
 03522                        "{0}_opencl={1}:-1:0",
 03523                        useBwdif ? "bwdif" : "yadif",
 03524                        doubleRateDeint ? "1" : "0");
 3525                }
 3526            }
 3527
 03528            if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
 3529            {
 03530                return string.Format(
 03531                    CultureInfo.InvariantCulture,
 03532                    "deinterlace_vaapi=rate={0}",
 03533                    doubleRateDeint ? "field" : "frame");
 3534            }
 3535
 03536            if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
 3537            {
 03538                return "deinterlace_qsv=mode=2";
 3539            }
 3540
 03541            if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
 3542            {
 03543                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3544
 03545                return string.Format(
 03546                    CultureInfo.InvariantCulture,
 03547                    "{0}_videotoolbox={1}:-1:0",
 03548                    useBwdif ? "bwdif" : "yadif",
 03549                    doubleRateDeint ? "1" : "0");
 3550            }
 3551
 03552            return string.Empty;
 3553        }
 3554
 3555        private string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat, bool forc
 3556        {
 03557            if (string.IsNullOrEmpty(hwTonemapSuffix))
 3558            {
 03559                return string.Empty;
 3560            }
 3561
 03562            var args = string.Empty;
 03563            var algorithm = options.TonemappingAlgorithm.ToString().ToLowerInvariant();
 03564            var mode = options.TonemappingMode.ToString().ToLowerInvariant();
 03565            var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 03566            var rangeString = range.ToString().ToLowerInvariant();
 3567
 03568            if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
 3569            {
 03570                var doVaVppProcamp = false;
 03571                var procampParams = string.Empty;
 03572                if (options.VppTonemappingBrightness != 0
 03573                    && options.VppTonemappingBrightness >= -100
 03574                    && options.VppTonemappingBrightness <= 100)
 3575                {
 03576                    procampParams += "procamp_vaapi=b={0}";
 03577                    doVaVppProcamp = true;
 3578                }
 3579
 03580                if (options.VppTonemappingContrast > 1
 03581                    && options.VppTonemappingContrast <= 10)
 3582                {
 03583                    procampParams += doVaVppProcamp ? ":c={1}" : "procamp_vaapi=c={1}";
 03584                    doVaVppProcamp = true;
 3585                }
 3586
 03587                args = procampParams + "{2}tonemap_vaapi=format={3}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
 3588
 03589                return string.Format(
 03590                        CultureInfo.InvariantCulture,
 03591                        args,
 03592                        options.VppTonemappingBrightness,
 03593                        options.VppTonemappingContrast,
 03594                        doVaVppProcamp ? "," : string.Empty,
 03595                        videoFormat ?? "nv12");
 3596            }
 3597            else
 3598            {
 03599                args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
 3600
 03601                var useLegacyTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode
 03602                                           && _legacyTonemapModes.Contains(options.TonemappingMode);
 3603
 03604                var useAdvancedTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegAdvancedTonemapMode
 03605                                              && _advancedTonemapModes.Contains(options.TonemappingMode);
 3606
 03607                if (useLegacyTonemapModes || useAdvancedTonemapModes)
 3608                {
 03609                    args += ":tonemap_mode={5}";
 3610                }
 3611
 03612                if (options.TonemappingParam != 0)
 3613                {
 03614                    args += ":param={6}";
 3615                }
 3616
 03617                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3618                {
 03619                    args += ":range={7}";
 3620                }
 3621            }
 3622
 03623            return string.Format(
 03624                    CultureInfo.InvariantCulture,
 03625                    args,
 03626                    hwTonemapSuffix,
 03627                    videoFormat ?? "nv12",
 03628                    algorithm,
 03629                    options.TonemappingPeak,
 03630                    options.TonemappingDesat,
 03631                    mode,
 03632                    options.TonemappingParam,
 03633                    rangeString);
 3634        }
 3635
 3636        private string GetLibplaceboFilter(
 3637            EncodingOptions options,
 3638            string videoFormat,
 3639            bool doTonemap,
 3640            int? videoWidth,
 3641            int? videoHeight,
 3642            int? requestedWidth,
 3643            int? requestedHeight,
 3644            int? requestedMaxWidth,
 3645            int? requestedMaxHeight,
 3646            bool forceFullRange)
 3647        {
 03648            var (outWidth, outHeight) = GetFixedOutputSize(
 03649                videoWidth,
 03650                videoHeight,
 03651                requestedWidth,
 03652                requestedHeight,
 03653                requestedMaxWidth,
 03654                requestedMaxHeight);
 3655
 03656            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03657            var isSizeFixed = !videoWidth.HasValue
 03658                || outWidth.Value != videoWidth.Value
 03659                || !videoHeight.HasValue
 03660                || outHeight.Value != videoHeight.Value;
 3661
 03662            var sizeArg = isSizeFixed ? (":w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty;
 03663            var formatArg = isFormatFixed ? (":format=" + videoFormat) : string.Empty;
 03664            var tonemapArg = string.Empty;
 3665
 03666            if (doTonemap)
 3667            {
 03668                var algorithm = options.TonemappingAlgorithm;
 03669                var algorithmString = "clip";
 03670                var mode = options.TonemappingMode;
 03671                var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 3672
 03673                if (algorithm == TonemappingAlgorithm.bt2390)
 3674                {
 03675                    algorithmString = "bt.2390";
 3676                }
 03677                else if (algorithm != TonemappingAlgorithm.none)
 3678                {
 03679                    algorithmString = algorithm.ToString().ToLowerInvariant();
 3680                }
 3681
 03682                tonemapArg = $":tonemapping={algorithmString}:peak_detect=0:color_primaries=bt709:color_trc=bt709:colors
 3683
 03684                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3685                {
 03686                    tonemapArg += ":range=" + range.ToString().ToLowerInvariant();
 3687                }
 3688            }
 3689
 03690            return string.Format(
 03691                CultureInfo.InvariantCulture,
 03692                "libplacebo=upscaler=none:downscaler=none{0}{1}{2}",
 03693                sizeArg,
 03694                formatArg,
 03695                tonemapArg);
 3696        }
 3697
 3698        public string GetVideoTransposeDirection(EncodingJobInfo state)
 3699        {
 03700            return (state.VideoStream?.Rotation ?? 0) switch
 03701            {
 03702                90 => "cclock",
 03703                180 => "reversal",
 03704                -90 => "clock",
 03705                -180 => "reversal",
 03706                _ => string.Empty
 03707            };
 3708        }
 3709
 3710        /// <summary>
 3711        /// Gets the parameter of software filter chain.
 3712        /// </summary>
 3713        /// <param name="state">Encoding state.</param>
 3714        /// <param name="options">Encoding options.</param>
 3715        /// <param name="vidEncoder">Video encoder to use.</param>
 3716        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3717        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetSwVidFilterChain(
 3718            EncodingJobInfo state,
 3719            EncodingOptions options,
 3720            string vidEncoder)
 3721        {
 03722            var inW = state.VideoStream?.Width;
 03723            var inH = state.VideoStream?.Height;
 03724            var reqW = state.BaseRequest.Width;
 03725            var reqH = state.BaseRequest.Height;
 03726            var reqMaxW = state.BaseRequest.MaxWidth;
 03727            var reqMaxH = state.BaseRequest.MaxHeight;
 03728            var threeDFormat = state.MediaSource.Video3DFormat;
 3729
 03730            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03731            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03732            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 03733            var isV4l2Encoder = vidEncoder.Contains("h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 3734
 03735            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03736            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03737            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03738            var doToneMap = IsSwTonemapAvailable(state, options);
 03739            var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI;
 3740
 03741            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03742            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03743            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 3744
 03745            var rotation = state.VideoStream?.Rotation ?? 0;
 03746            var swapWAndH = Math.Abs(rotation) == 90;
 03747            var swpInW = swapWAndH ? inH : inW;
 03748            var swpInH = swapWAndH ? inW : inH;
 3749
 3750            /* Make main filters for video stream */
 03751            var mainFilters = new List<string>();
 3752
 03753            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doToneMap));
 3754
 3755            // INPUT sw surface(memory/copy-back from vram)
 3756            // sw deint
 03757            if (doDeintH2645)
 3758            {
 03759                var deintFilter = GetSwDeinterlaceFilter(state, options);
 03760                mainFilters.Add(deintFilter);
 3761            }
 3762
 03763            var outFormat = isSwDecoder ? "yuv420p" : "nv12";
 03764            var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, r
 03765            if (isVaapiEncoder)
 3766            {
 03767                outFormat = "nv12";
 3768            }
 03769            else if (isV4l2Encoder)
 3770            {
 03771                outFormat = "yuv420p";
 3772            }
 3773
 3774            // sw scale
 03775            mainFilters.Add(swScaleFilter);
 3776
 3777            // sw tonemap
 03778            if (doToneMap)
 3779            {
 3780                // tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary
 03781                var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat;
 03782                var tonemapArgString = "tonemapx=tonemap={0}:desat={1}:peak={2}:t=bt709:m=bt709:p=bt709:format={3}";
 3783
 03784                if (options.TonemappingParam != 0)
 3785                {
 03786                    tonemapArgString += ":param={4}";
 3787                }
 3788
 03789                var range = options.TonemappingRange;
 03790                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3791                {
 03792                    tonemapArgString += ":range={5}";
 3793                }
 3794
 03795                var tonemapArgs = string.Format(
 03796                    CultureInfo.InvariantCulture,
 03797                    tonemapArgString,
 03798                    options.TonemappingAlgorithm,
 03799                    options.TonemappingDesat,
 03800                    options.TonemappingPeak,
 03801                    tonemapFormat,
 03802                    options.TonemappingParam,
 03803                    options.TonemappingRange);
 3804
 03805                mainFilters.Add(tonemapArgs);
 3806            }
 3807            else
 3808            {
 3809                // OUTPUT yuv420p/nv12 surface(memory)
 03810                mainFilters.Add("format=" + outFormat);
 3811            }
 3812
 3813            /* Make sub and overlay filters for subtitle stream */
 03814            var subFilters = new List<string>();
 03815            var overlayFilters = new List<string>();
 03816            if (hasTextSubs)
 3817            {
 3818                // subtitles=f='*.ass':alpha=0
 03819                var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03820                mainFilters.Add(textSubtitlesFilter);
 3821            }
 03822            else if (hasGraphicalSubs)
 3823            {
 03824                var subW = state.SubtitleStream?.Width;
 03825                var subH = state.SubtitleStream?.Height;
 03826                var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW
 03827                subFilters.Add(subPreProcFilters);
 03828                overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 3829            }
 3830
 03831            return (mainFilters, subFilters, overlayFilters);
 3832        }
 3833
 3834        /// <summary>
 3835        /// Gets the parameter of Nvidia NVENC filter chain.
 3836        /// </summary>
 3837        /// <param name="state">Encoding state.</param>
 3838        /// <param name="options">Encoding options.</param>
 3839        /// <param name="vidEncoder">Video encoder to use.</param>
 3840        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3841        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFilterChain(
 3842            EncodingJobInfo state,
 3843            EncodingOptions options,
 3844            string vidEncoder)
 3845        {
 03846            if (options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 3847            {
 03848                return (null, null, null);
 3849            }
 3850
 03851            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03852            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03853            var isSwEncoder = !vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 3854
 3855            // legacy cuvid pipeline(copy-back)
 03856            if ((isSwDecoder && isSwEncoder)
 03857                || !IsCudaFullSupported()
 03858                || !_mediaEncoder.SupportsFilter("alphasrc"))
 3859            {
 03860                return GetSwVidFilterChain(state, options, vidEncoder);
 3861            }
 3862
 3863            // preferred nvdec/cuvid + cuda filters + nvenc pipeline
 03864            return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 3865        }
 3866
 3867        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFiltersPrefe
 3868            EncodingJobInfo state,
 3869            EncodingOptions options,
 3870            string vidDecoder,
 3871            string vidEncoder)
 3872        {
 03873            var inW = state.VideoStream?.Width;
 03874            var inH = state.VideoStream?.Height;
 03875            var reqW = state.BaseRequest.Width;
 03876            var reqH = state.BaseRequest.Height;
 03877            var reqMaxW = state.BaseRequest.MaxWidth;
 03878            var reqMaxH = state.BaseRequest.MaxHeight;
 03879            var threeDFormat = state.MediaSource.Video3DFormat;
 3880
 03881            var isNvDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 03882            var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 03883            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03884            var isSwEncoder = !isNvencEncoder;
 03885            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 03886            var isCuInCuOut = isNvDecoder && isNvencEncoder;
 3887
 03888            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03889            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03890            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03891            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03892            var doCuTonemap = IsHwTonemapAvailable(state, options);
 3893
 03894            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03895            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03896            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 03897            var hasAssSubs = hasSubs
 03898                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 03899                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 03900            var subW = state.SubtitleStream?.Width;
 03901            var subH = state.SubtitleStream?.Height;
 3902
 03903            var rotation = state.VideoStream?.Rotation ?? 0;
 03904            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 03905            var doCuTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_cuda");
 03906            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isNvDecoder && doCuTranspose));
 03907            var swpInW = swapWAndH ? inH : inW;
 03908            var swpInH = swapWAndH ? inW : inH;
 3909
 3910            /* Make main filters for video stream */
 03911            var mainFilters = new List<string>();
 3912
 03913            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doCuTonemap));
 3914
 03915            if (isSwDecoder)
 3916            {
 3917                // INPUT sw surface(memory)
 3918                // sw deint
 03919                if (doDeintH2645)
 3920                {
 03921                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 03922                    mainFilters.Add(swDeintFilter);
 3923                }
 3924
 03925                var outFormat = doCuTonemap ? "yuv420p10le" : "yuv420p";
 03926                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 3927                // sw scale
 03928                mainFilters.Add(swScaleFilter);
 03929                mainFilters.Add($"format={outFormat}");
 3930
 3931                // sw => hw
 03932                if (doCuTonemap)
 3933                {
 03934                    mainFilters.Add("hwupload=derive_device=cuda");
 3935                }
 3936            }
 3937
 03938            if (isNvDecoder)
 3939            {
 3940                // INPUT cuda surface(vram)
 3941                // hw deint
 03942                if (doDeintH2645)
 3943                {
 03944                    var deintFilter = GetHwDeinterlaceFilter(state, options, "cuda");
 03945                    mainFilters.Add(deintFilter);
 3946                }
 3947
 3948                // hw transpose
 03949                if (doCuTranspose)
 3950                {
 03951                    mainFilters.Add($"transpose_cuda=dir={transposeDir}");
 3952                }
 3953
 03954                var isRext = IsVideoStreamHevcRext(state);
 03955                var outFormat = doCuTonemap ? (isRext ? "p010" : string.Empty) : "yuv420p";
 03956                var hwScaleFilter = GetHwScaleFilter("scale", "cuda", outFormat, false, swpInW, swpInH, reqW, reqH, reqM
 3957                // hw scale
 03958                mainFilters.Add(hwScaleFilter);
 3959            }
 3960
 3961            // hw tonemap
 03962            if (doCuTonemap)
 3963            {
 03964                var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p", isMjpegEncoder);
 03965                mainFilters.Add(tonemapFilter);
 3966            }
 3967
 03968            var memoryOutput = false;
 03969            var isUploadForCuTonemap = isSwDecoder && doCuTonemap;
 03970            if ((isNvDecoder && isSwEncoder) || (isUploadForCuTonemap && hasSubs))
 3971            {
 03972                memoryOutput = true;
 3973
 3974                // OUTPUT yuv420p surface(memory)
 03975                mainFilters.Add("hwdownload");
 03976                mainFilters.Add("format=yuv420p");
 3977            }
 3978
 3979            // OUTPUT yuv420p surface(memory)
 03980            if (isSwDecoder && isNvencEncoder && !isUploadForCuTonemap)
 3981            {
 03982                memoryOutput = true;
 3983            }
 3984
 03985            if (memoryOutput)
 3986            {
 3987                // text subtitles
 03988                if (hasTextSubs)
 3989                {
 03990                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03991                    mainFilters.Add(textSubtitlesFilter);
 3992                }
 3993            }
 3994
 3995            // OUTPUT cuda(yuv420p) surface(vram)
 3996
 3997            /* Make sub and overlay filters for subtitle stream */
 03998            var subFilters = new List<string>();
 03999            var overlayFilters = new List<string>();
 04000            if (isCuInCuOut)
 4001            {
 04002                if (hasSubs)
 4003                {
 04004                    var alphaFormatOpt = string.Empty;
 04005                    if (hasGraphicalSubs)
 4006                    {
 04007                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04008                        subFilters.Add(subPreProcFilters);
 04009                        subFilters.Add("format=yuva420p");
 4010                    }
 04011                    else if (hasTextSubs)
 4012                    {
 04013                        var framerate = state.VideoStream?.RealFrameRate;
 04014                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4015
 4016                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04017                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04018                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04019                        subFilters.Add(alphaSrcFilter);
 04020                        subFilters.Add("format=yuva420p");
 04021                        subFilters.Add(subTextSubtitlesFilter);
 4022
 04023                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayCudaAlphaFormat)
 04024                            ? ":alpha_format=premultiplied" : string.Empty;
 4025                    }
 4026
 04027                    subFilters.Add("hwupload=derive_device=cuda");
 04028                    overlayFilters.Add($"overlay_cuda=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 4029                }
 4030            }
 4031            else
 4032            {
 04033                if (hasGraphicalSubs)
 4034                {
 04035                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04036                    subFilters.Add(subPreProcFilters);
 04037                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4038                }
 4039            }
 4040
 04041            return (mainFilters, subFilters, overlayFilters);
 4042        }
 4043
 4044        /// <summary>
 4045        /// Gets the parameter of AMD AMF filter chain.
 4046        /// </summary>
 4047        /// <param name="state">Encoding state.</param>
 4048        /// <param name="options">Encoding options.</param>
 4049        /// <param name="vidEncoder">Video encoder to use.</param>
 4050        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4051        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVidFilterChain(
 4052            EncodingJobInfo state,
 4053            EncodingOptions options,
 4054            string vidEncoder)
 4055        {
 04056            if (options.HardwareAccelerationType != HardwareAccelerationType.amf)
 4057            {
 04058                return (null, null, null);
 4059            }
 4060
 04061            var isWindows = OperatingSystem.IsWindows();
 04062            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04063            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04064            var isSwEncoder = !vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04065            var isAmfDx11OclSupported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va") && IsOpenclFullSupported()
 4066
 4067            // legacy d3d11va pipeline(copy-back)
 04068            if ((isSwDecoder && isSwEncoder)
 04069                || !isAmfDx11OclSupported
 04070                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4071            {
 04072                return GetSwVidFilterChain(state, options, vidEncoder);
 4073            }
 4074
 4075            // preferred d3d11va + opencl filters + amf pipeline
 04076            return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4077        }
 4078
 4079        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdDx11VidFiltersPref
 4080            EncodingJobInfo state,
 4081            EncodingOptions options,
 4082            string vidDecoder,
 4083            string vidEncoder)
 4084        {
 04085            var inW = state.VideoStream?.Width;
 04086            var inH = state.VideoStream?.Height;
 04087            var reqW = state.BaseRequest.Width;
 04088            var reqH = state.BaseRequest.Height;
 04089            var reqMaxW = state.BaseRequest.MaxWidth;
 04090            var reqMaxH = state.BaseRequest.MaxHeight;
 04091            var threeDFormat = state.MediaSource.Video3DFormat;
 4092
 04093            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04094            var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04095            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04096            var isSwEncoder = !isAmfEncoder;
 04097            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04098            var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder;
 4099
 04100            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04101            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04102            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04103            var doOclTonemap = IsHwTonemapAvailable(state, options);
 4104
 04105            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04106            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04107            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04108            var hasAssSubs = hasSubs
 04109                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04110                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04111            var subW = state.SubtitleStream?.Width;
 04112            var subH = state.SubtitleStream?.Height;
 4113
 04114            var rotation = state.VideoStream?.Rotation ?? 0;
 04115            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04116            var doOclTranspose = !string.IsNullOrEmpty(transposeDir)
 04117                && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TransposeOpenclReversal);
 04118            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isD3d11vaDecoder && doOclTranspose));
 04119            var swpInW = swapWAndH ? inH : inW;
 04120            var swpInH = swapWAndH ? inW : inH;
 4121
 4122            /* Make main filters for video stream */
 04123            var mainFilters = new List<string>();
 4124
 04125            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 4126
 04127            if (isSwDecoder)
 4128            {
 4129                // INPUT sw surface(memory)
 4130                // sw deint
 04131                if (doDeintH2645)
 4132                {
 04133                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04134                    mainFilters.Add(swDeintFilter);
 4135                }
 4136
 04137                var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
 04138                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 4139                // sw scale
 04140                mainFilters.Add(swScaleFilter);
 04141                mainFilters.Add($"format={outFormat}");
 4142
 4143                // keep video at memory except ocl tonemap,
 4144                // since the overhead caused by hwupload >>> using sw filter.
 4145                // sw => hw
 04146                if (doOclTonemap)
 4147                {
 04148                    mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=24");
 04149                    mainFilters.Add("format=d3d11");
 04150                    mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4151                }
 4152            }
 4153
 04154            if (isD3d11vaDecoder)
 4155            {
 4156                // INPUT d3d11 surface(vram)
 4157                // map from d3d11va to opencl via d3d11-opencl interop.
 04158                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4159
 4160                // hw deint
 04161                if (doDeintH2645)
 4162                {
 04163                    var deintFilter = GetHwDeinterlaceFilter(state, options, "opencl");
 04164                    mainFilters.Add(deintFilter);
 4165                }
 4166
 4167                // hw transpose
 04168                if (doOclTranspose)
 4169                {
 04170                    mainFilters.Add($"transpose_opencl=dir={transposeDir}");
 4171                }
 4172
 04173                var outFormat = doOclTonemap ? string.Empty : "nv12";
 04174                var hwScaleFilter = GetHwScaleFilter("scale", "opencl", outFormat, false, swpInW, swpInH, reqW, reqH, re
 4175                // hw scale
 04176                mainFilters.Add(hwScaleFilter);
 4177            }
 4178
 4179            // hw tonemap
 04180            if (doOclTonemap)
 4181            {
 04182                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04183                mainFilters.Add(tonemapFilter);
 4184            }
 4185
 04186            var memoryOutput = false;
 04187            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04188            if (isD3d11vaDecoder && isSwEncoder)
 4189            {
 04190                memoryOutput = true;
 4191
 4192                // OUTPUT nv12 surface(memory)
 4193                // prefer hwmap to hwdownload on opencl.
 04194                var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap=mode=read";
 04195                mainFilters.Add(hwTransferFilter);
 04196                mainFilters.Add("format=nv12");
 4197            }
 4198
 4199            // OUTPUT yuv420p surface
 04200            if (isSwDecoder && isAmfEncoder && !isUploadForOclTonemap)
 4201            {
 04202                memoryOutput = true;
 4203            }
 4204
 04205            if (memoryOutput)
 4206            {
 4207                // text subtitles
 04208                if (hasTextSubs)
 4209                {
 04210                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04211                    mainFilters.Add(textSubtitlesFilter);
 4212                }
 4213            }
 4214
 04215            if ((isDxInDxOut || isUploadForOclTonemap) && !hasSubs)
 4216            {
 4217                // OUTPUT d3d11(nv12) surface(vram)
 4218                // reverse-mapping via d3d11-opencl interop.
 04219                mainFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04220                mainFilters.Add("format=d3d11");
 4221            }
 4222
 4223            /* Make sub and overlay filters for subtitle stream */
 04224            var subFilters = new List<string>();
 04225            var overlayFilters = new List<string>();
 04226            if (isDxInDxOut || isUploadForOclTonemap)
 4227            {
 04228                if (hasSubs)
 4229                {
 04230                    var alphaFormatOpt = string.Empty;
 04231                    if (hasGraphicalSubs)
 4232                    {
 04233                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04234                        subFilters.Add(subPreProcFilters);
 04235                        subFilters.Add("format=yuva420p");
 4236                    }
 04237                    else if (hasTextSubs)
 4238                    {
 04239                        var framerate = state.VideoStream?.RealFrameRate;
 04240                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4241
 4242                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04243                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04244                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04245                        subFilters.Add(alphaSrcFilter);
 04246                        subFilters.Add("format=yuva420p");
 04247                        subFilters.Add(subTextSubtitlesFilter);
 4248
 04249                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclAlphaForma
 04250                            ? ":alpha_format=premultiplied" : string.Empty;
 4251                    }
 4252
 04253                    subFilters.Add("hwupload=derive_device=opencl");
 04254                    overlayFilters.Add($"overlay_opencl=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 04255                    overlayFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04256                    overlayFilters.Add("format=d3d11");
 4257                }
 4258            }
 04259            else if (memoryOutput)
 4260            {
 04261                if (hasGraphicalSubs)
 4262                {
 04263                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04264                    subFilters.Add(subPreProcFilters);
 04265                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4266                }
 4267            }
 4268
 04269            return (mainFilters, subFilters, overlayFilters);
 4270        }
 4271
 4272        /// <summary>
 4273        /// Gets the parameter of Intel QSV filter chain.
 4274        /// </summary>
 4275        /// <param name="state">Encoding state.</param>
 4276        /// <param name="options">Encoding options.</param>
 4277        /// <param name="vidEncoder">Video encoder to use.</param>
 4278        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4279        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVidFilterChain(
 4280            EncodingJobInfo state,
 4281            EncodingOptions options,
 4282            string vidEncoder)
 4283        {
 04284            if (options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 4285            {
 04286                return (null, null, null);
 4287            }
 4288
 04289            var isWindows = OperatingSystem.IsWindows();
 04290            var isLinux = OperatingSystem.IsLinux();
 04291            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04292            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04293            var isSwEncoder = !vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04294            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 04295            var isIntelDx11OclSupported = isWindows
 04296                && _mediaEncoder.SupportsHwaccel("d3d11va")
 04297                && isQsvOclSupported;
 04298            var isIntelVaapiOclSupported = isLinux
 04299                && IsVaapiSupported(state)
 04300                && isQsvOclSupported;
 4301
 4302            // legacy qsv pipeline(copy-back)
 04303            if ((isSwDecoder && isSwEncoder)
 04304                || (!isIntelVaapiOclSupported && !isIntelDx11OclSupported)
 04305                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4306            {
 04307                return GetSwVidFilterChain(state, options, vidEncoder);
 4308            }
 4309
 4310            // preferred qsv(vaapi) + opencl filters pipeline
 04311            if (isIntelVaapiOclSupported)
 4312            {
 04313                return GetIntelQsvVaapiVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4314            }
 4315
 4316            // preferred qsv(d3d11) + opencl filters pipeline
 04317            if (isIntelDx11OclSupported)
 4318            {
 04319                return GetIntelQsvDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4320            }
 4321
 04322            return (null, null, null);
 4323        }
 4324
 4325        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvDx11VidFilter
 4326            EncodingJobInfo state,
 4327            EncodingOptions options,
 4328            string vidDecoder,
 4329            string vidEncoder)
 4330        {
 04331            var inW = state.VideoStream?.Width;
 04332            var inH = state.VideoStream?.Height;
 04333            var reqW = state.BaseRequest.Width;
 04334            var reqH = state.BaseRequest.Height;
 04335            var reqMaxW = state.BaseRequest.MaxWidth;
 04336            var reqMaxH = state.BaseRequest.MaxHeight;
 04337            var threeDFormat = state.MediaSource.Video3DFormat;
 4338
 04339            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04340            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04341            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04342            var isHwDecoder = isD3d11vaDecoder || isQsvDecoder;
 04343            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04344            var isSwEncoder = !isQsvEncoder;
 04345            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04346            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4347
 04348            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04349            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04350            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04351            var doVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04352            var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options);
 04353            var doTonemap = doVppTonemap || doOclTonemap;
 4354
 04355            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04356            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04357            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04358            var hasAssSubs = hasSubs
 04359                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04360                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04361            var subW = state.SubtitleStream?.Width;
 04362            var subH = state.SubtitleStream?.Height;
 4363
 04364            var rotation = state.VideoStream?.Rotation ?? 0;
 04365            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04366            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04367            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isD3d11vaDecoder || isQsvDecoder) && doVppTran
 04368            var swpInW = swapWAndH ? inH : inW;
 04369            var swpInH = swapWAndH ? inW : inH;
 4370
 4371            /* Make main filters for video stream */
 04372            var mainFilters = new List<string>();
 4373
 04374            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4375
 04376            if (isSwDecoder)
 4377            {
 4378                // INPUT sw surface(memory)
 4379                // sw deint
 04380                if (doDeintH2645)
 4381                {
 04382                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04383                    mainFilters.Add(swDeintFilter);
 4384                }
 4385
 04386                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04387                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04388                if (isMjpegEncoder && !doOclTonemap)
 4389                {
 4390                    // sw decoder + hw mjpeg encoder
 04391                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4392                }
 4393
 4394                // sw scale
 04395                mainFilters.Add(swScaleFilter);
 04396                mainFilters.Add($"format={outFormat}");
 4397
 4398                // keep video at memory except ocl tonemap,
 4399                // since the overhead caused by hwupload >>> using sw filter.
 4400                // sw => hw
 04401                if (doOclTonemap)
 4402                {
 04403                    mainFilters.Add("hwupload=derive_device=opencl");
 4404                }
 4405            }
 04406            else if (isD3d11vaDecoder || isQsvDecoder)
 4407            {
 04408                var isRext = IsVideoStreamHevcRext(state);
 04409                var twoPassVppTonemap = false;
 04410                var doVppFullRangeOut = isMjpegEncoder
 04411                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04412                var doVppScaleModeHq = isMjpegEncoder
 04413                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 04414                var doVppProcamp = false;
 04415                var procampParams = string.Empty;
 04416                var procampParamsString = string.Empty;
 04417                if (doVppTonemap)
 4418                {
 04419                    if (isRext)
 4420                    {
 4421                        // VPP tonemap requires p010 input
 04422                        twoPassVppTonemap = true;
 4423                    }
 4424
 04425                    if (options.VppTonemappingBrightness != 0
 04426                        && options.VppTonemappingBrightness >= -100
 04427                        && options.VppTonemappingBrightness <= 100)
 4428                    {
 04429                        procampParamsString += ":brightness={0}";
 04430                        twoPassVppTonemap = doVppProcamp = true;
 4431                    }
 4432
 04433                    if (options.VppTonemappingContrast > 1
 04434                        && options.VppTonemappingContrast <= 10)
 4435                    {
 04436                        procampParamsString += ":contrast={1}";
 04437                        twoPassVppTonemap = doVppProcamp = true;
 4438                    }
 4439
 04440                    if (doVppProcamp)
 4441                    {
 04442                        procampParamsString += ":procamp=1:async_depth=2";
 04443                        procampParams = string.Format(
 04444                            CultureInfo.InvariantCulture,
 04445                            procampParamsString,
 04446                            options.VppTonemappingBrightness,
 04447                            options.VppTonemappingContrast);
 4448                    }
 4449                }
 4450
 04451                var outFormat = doOclTonemap ? ((doVppTranspose || isRext) ? "p010" : string.Empty) : "nv12";
 04452                outFormat = twoPassVppTonemap ? "p010" : outFormat;
 4453
 04454                var swapOutputWandH = doVppTranspose && swapWAndH;
 04455                var hwScaleFilter = GetHwScaleFilter("vpp", "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, req
 4456
 4457                // d3d11va doesn't support dynamic pool size, use vpp filter ctx to relay
 4458                // to prevent encoder async and bframes from exhausting the decoder pool.
 04459                if (!string.IsNullOrEmpty(hwScaleFilter) && isD3d11vaDecoder)
 4460                {
 04461                    hwScaleFilter += ":passthrough=0";
 4462                }
 4463
 04464                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose)
 4465                {
 04466                    hwScaleFilter += $":transpose={transposeDir}";
 4467                }
 4468
 04469                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4470                {
 04471                    hwScaleFilter += (doVppFullRangeOut && !doOclTonemap) ? ":out_range=pc" : string.Empty;
 04472                    hwScaleFilter += doVppScaleModeHq ? ":scale_mode=hq" : string.Empty;
 4473                }
 4474
 04475                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTonemap)
 4476                {
 04477                    hwScaleFilter += doVppProcamp ? procampParams : (twoPassVppTonemap ? string.Empty : ":tonemap=1");
 4478                }
 4479
 04480                if (isD3d11vaDecoder)
 4481                {
 04482                    if (!string.IsNullOrEmpty(hwScaleFilter) || doDeintH2645)
 4483                    {
 4484                        // INPUT d3d11 surface(vram)
 4485                        // map from d3d11va to qsv.
 04486                        mainFilters.Add("hwmap=derive_device=qsv");
 4487                    }
 4488                }
 4489
 4490                // hw deint
 04491                if (doDeintH2645)
 4492                {
 04493                    var deintFilter = GetHwDeinterlaceFilter(state, options, "qsv");
 04494                    mainFilters.Add(deintFilter);
 4495                }
 4496
 4497                // hw transpose & scale & tonemap(w/o procamp)
 04498                mainFilters.Add(hwScaleFilter);
 4499
 4500                // hw tonemap(w/ procamp)
 04501                if (doVppTonemap && twoPassVppTonemap)
 4502                {
 04503                    mainFilters.Add("vpp_qsv=tonemap=1:format=nv12:async_depth=2");
 4504                }
 4505
 4506                // force bt709 just in case vpp tonemap is not triggered or using MSDK instead of VPL.
 04507                if (doVppTonemap)
 4508                {
 04509                    mainFilters.Add(GetOverwriteColorPropertiesParam(state, false));
 4510                }
 4511            }
 4512
 04513            if (doOclTonemap && isHwDecoder)
 4514            {
 4515                // map from qsv to opencl via qsv(d3d11)-opencl interop.
 04516                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4517            }
 4518
 4519            // hw tonemap
 04520            if (doOclTonemap)
 4521            {
 04522                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04523                mainFilters.Add(tonemapFilter);
 4524            }
 4525
 04526            var memoryOutput = false;
 04527            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04528            var isHwmapUsable = isSwEncoder && doOclTonemap;
 04529            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4530            {
 04531                memoryOutput = true;
 4532
 4533                // OUTPUT nv12 surface(memory)
 4534                // prefer hwmap to hwdownload on opencl.
 4535                // qsv hwmap is not fully implemented for the time being.
 04536                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04537                mainFilters.Add("format=nv12");
 4538            }
 4539
 4540            // OUTPUT nv12 surface(memory)
 04541            if (isSwDecoder && isQsvEncoder)
 4542            {
 04543                memoryOutput = true;
 4544            }
 4545
 04546            if (memoryOutput)
 4547            {
 4548                // text subtitles
 04549                if (hasTextSubs)
 4550                {
 04551                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04552                    mainFilters.Add(textSubtitlesFilter);
 4553                }
 4554            }
 4555
 04556            if (isQsvInQsvOut && doOclTonemap)
 4557            {
 4558                // OUTPUT qsv(nv12) surface(vram)
 4559                // reverse-mapping via qsv(d3d11)-opencl interop.
 04560                mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1");
 04561                mainFilters.Add("format=qsv");
 4562            }
 4563
 4564            /* Make sub and overlay filters for subtitle stream */
 04565            var subFilters = new List<string>();
 04566            var overlayFilters = new List<string>();
 04567            if (isQsvInQsvOut)
 4568            {
 04569                if (hasSubs)
 4570                {
 04571                    if (hasGraphicalSubs)
 4572                    {
 4573                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04574                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04575                        subFilters.Add(subPreProcFilters);
 04576                        subFilters.Add("format=bgra");
 4577                    }
 04578                    else if (hasTextSubs)
 4579                    {
 04580                        var framerate = state.VideoStream?.RealFrameRate;
 04581                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4582
 4583                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 04584                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04585                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04586                        subFilters.Add(alphaSrcFilter);
 04587                        subFilters.Add("format=bgra");
 04588                        subFilters.Add(subTextSubtitlesFilter);
 4589                    }
 4590
 4591                    // qsv requires a fixed pool size.
 4592                    // default to 64 otherwise it will fail on certain iGPU.
 04593                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4594
 04595                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04596                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04597                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04598                        : string.Empty;
 04599                    var overlayQsvFilter = string.Format(
 04600                        CultureInfo.InvariantCulture,
 04601                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04602                        overlaySize);
 04603                    overlayFilters.Add(overlayQsvFilter);
 4604                }
 4605            }
 04606            else if (memoryOutput)
 4607            {
 04608                if (hasGraphicalSubs)
 4609                {
 04610                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04611                    subFilters.Add(subPreProcFilters);
 04612                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4613                }
 4614            }
 4615
 04616            return (mainFilters, subFilters, overlayFilters);
 4617        }
 4618
 4619        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvVaapiVidFilte
 4620            EncodingJobInfo state,
 4621            EncodingOptions options,
 4622            string vidDecoder,
 4623            string vidEncoder)
 4624        {
 04625            var inW = state.VideoStream?.Width;
 04626            var inH = state.VideoStream?.Height;
 04627            var reqW = state.BaseRequest.Width;
 04628            var reqH = state.BaseRequest.Height;
 04629            var reqMaxW = state.BaseRequest.MaxWidth;
 04630            var reqMaxH = state.BaseRequest.MaxHeight;
 04631            var threeDFormat = state.MediaSource.Video3DFormat;
 4632
 04633            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04634            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04635            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04636            var isHwDecoder = isVaapiDecoder || isQsvDecoder;
 04637            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04638            var isSwEncoder = !isQsvEncoder;
 04639            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04640            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4641
 04642            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04643            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04644            var doVaVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04645            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04646            var doTonemap = doVaVppTonemap || doOclTonemap;
 04647            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4648
 04649            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04650            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04651            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04652            var hasAssSubs = hasSubs
 04653                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04654                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04655            var subW = state.SubtitleStream?.Width;
 04656            var subH = state.SubtitleStream?.Height;
 4657
 04658            var rotation = state.VideoStream?.Rotation ?? 0;
 04659            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04660            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04661            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isVaapiDecoder || isQsvDecoder) && doVppTransp
 04662            var swpInW = swapWAndH ? inH : inW;
 04663            var swpInH = swapWAndH ? inW : inH;
 4664
 4665            /* Make main filters for video stream */
 04666            var mainFilters = new List<string>();
 4667
 04668            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4669
 04670            if (isSwDecoder)
 4671            {
 4672                // INPUT sw surface(memory)
 4673                // sw deint
 04674                if (doDeintH2645)
 4675                {
 04676                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04677                    mainFilters.Add(swDeintFilter);
 4678                }
 4679
 04680                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04681                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04682                if (isMjpegEncoder && !doOclTonemap)
 4683                {
 4684                    // sw decoder + hw mjpeg encoder
 04685                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4686                }
 4687
 4688                // sw scale
 04689                mainFilters.Add(swScaleFilter);
 04690                mainFilters.Add($"format={outFormat}");
 4691
 4692                // keep video at memory except ocl tonemap,
 4693                // since the overhead caused by hwupload >>> using sw filter.
 4694                // sw => hw
 04695                if (doOclTonemap)
 4696                {
 04697                    mainFilters.Add("hwupload=derive_device=opencl");
 4698                }
 4699            }
 04700            else if (isVaapiDecoder || isQsvDecoder)
 4701            {
 04702                var hwFilterSuffix = isVaapiDecoder ? "vaapi" : "qsv";
 04703                var isRext = IsVideoStreamHevcRext(state);
 04704                var doVppFullRangeOut = isMjpegEncoder
 04705                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04706                var doVppScaleModeHq = isMjpegEncoder
 04707                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 4708
 4709                // INPUT vaapi/qsv surface(vram)
 4710                // hw deint
 04711                if (doDeintH2645)
 4712                {
 04713                    var deintFilter = GetHwDeinterlaceFilter(state, options, hwFilterSuffix);
 04714                    mainFilters.Add(deintFilter);
 4715                }
 4716
 4717                // hw transpose(vaapi vpp)
 04718                if (isVaapiDecoder && doVppTranspose)
 4719                {
 04720                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 4721                }
 4722
 04723                var outFormat = doTonemap ? (((isQsvDecoder && doVppTranspose) || isRext) ? "p010" : string.Empty) : "nv
 04724                var swapOutputWandH = isQsvDecoder && doVppTranspose && swapWAndH;
 04725                var hwScalePrefix = isQsvDecoder ? "vpp" : "scale";
 04726                var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, hwFilterSuffix, outFormat, swapOutputWandH, swpInW, 
 4727
 04728                if (!string.IsNullOrEmpty(hwScaleFilter) && isQsvDecoder && doVppTranspose)
 4729                {
 04730                    hwScaleFilter += $":transpose={transposeDir}";
 4731                }
 4732
 04733                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4734                {
 04735                    hwScaleFilter += ((isQsvDecoder && !doVppFullRangeOut) || doOclTonemap) ? string.Empty : ":out_range
 04736                    hwScaleFilter += isQsvDecoder ? (doVppScaleModeHq ? ":scale_mode=hq" : string.Empty) : ":mode=hq";
 4737                }
 4738
 4739                // allocate extra pool sizes for vaapi vpp scale
 04740                if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder)
 4741                {
 04742                    hwScaleFilter += ":extra_hw_frames=24";
 4743                }
 4744
 4745                // hw transpose(qsv vpp) & scale
 04746                mainFilters.Add(hwScaleFilter);
 4747            }
 4748
 4749            // vaapi vpp tonemap
 04750            if (doVaVppTonemap && isHwDecoder)
 4751            {
 04752                if (isQsvDecoder)
 4753                {
 4754                    // map from qsv to vaapi.
 04755                    mainFilters.Add("hwmap=derive_device=vaapi");
 04756                    mainFilters.Add("format=vaapi");
 4757                }
 4758
 04759                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 04760                mainFilters.Add(tonemapFilter);
 4761
 04762                if (isQsvDecoder)
 4763                {
 4764                    // map from vaapi to qsv.
 04765                    mainFilters.Add("hwmap=derive_device=qsv");
 04766                    mainFilters.Add("format=qsv");
 4767                }
 4768            }
 4769
 04770            if (doOclTonemap && isHwDecoder)
 4771            {
 4772                // map from qsv to opencl via qsv(vaapi)-opencl interop.
 04773                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4774            }
 4775
 4776            // ocl tonemap
 04777            if (doOclTonemap)
 4778            {
 04779                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04780                mainFilters.Add(tonemapFilter);
 4781            }
 4782
 04783            var memoryOutput = false;
 04784            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04785            var isHwmapUsable = isSwEncoder && (doOclTonemap || isVaapiDecoder);
 04786            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4787            {
 04788                memoryOutput = true;
 4789
 4790                // OUTPUT nv12 surface(memory)
 4791                // prefer hwmap to hwdownload on opencl/vaapi.
 4792                // qsv hwmap is not fully implemented for the time being.
 04793                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04794                mainFilters.Add("format=nv12");
 4795            }
 4796
 4797            // OUTPUT nv12 surface(memory)
 04798            if (isSwDecoder && isQsvEncoder)
 4799            {
 04800                memoryOutput = true;
 4801            }
 4802
 04803            if (memoryOutput)
 4804            {
 4805                // text subtitles
 04806                if (hasTextSubs)
 4807                {
 04808                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04809                    mainFilters.Add(textSubtitlesFilter);
 4810                }
 4811            }
 4812
 04813            if (isQsvInQsvOut)
 4814            {
 04815                if (doOclTonemap)
 4816                {
 4817                    // OUTPUT qsv(nv12) surface(vram)
 4818                    // reverse-mapping via qsv(vaapi)-opencl interop.
 4819                    // add extra pool size to avoid the 'cannot allocate memory' error on hevc_qsv.
 04820                    mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1:extra_hw_frames=16");
 04821                    mainFilters.Add("format=qsv");
 4822                }
 04823                else if (isVaapiDecoder)
 4824                {
 04825                    mainFilters.Add("hwmap=derive_device=qsv");
 04826                    mainFilters.Add("format=qsv");
 4827                }
 4828            }
 4829
 4830            /* Make sub and overlay filters for subtitle stream */
 04831            var subFilters = new List<string>();
 04832            var overlayFilters = new List<string>();
 04833            if (isQsvInQsvOut)
 4834            {
 04835                if (hasSubs)
 4836                {
 04837                    if (hasGraphicalSubs)
 4838                    {
 4839                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04840                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04841                        subFilters.Add(subPreProcFilters);
 04842                        subFilters.Add("format=bgra");
 4843                    }
 04844                    else if (hasTextSubs)
 4845                    {
 04846                        var framerate = state.VideoStream?.RealFrameRate;
 04847                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4848
 04849                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04850                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04851                        subFilters.Add(alphaSrcFilter);
 04852                        subFilters.Add("format=bgra");
 04853                        subFilters.Add(subTextSubtitlesFilter);
 4854                    }
 4855
 4856                    // qsv requires a fixed pool size.
 4857                    // default to 64 otherwise it will fail on certain iGPU.
 04858                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4859
 04860                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04861                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04862                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04863                        : string.Empty;
 04864                    var overlayQsvFilter = string.Format(
 04865                        CultureInfo.InvariantCulture,
 04866                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04867                        overlaySize);
 04868                    overlayFilters.Add(overlayQsvFilter);
 4869                }
 4870            }
 04871            else if (memoryOutput)
 4872            {
 04873                if (hasGraphicalSubs)
 4874                {
 04875                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04876                    subFilters.Add(subPreProcFilters);
 04877                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4878                }
 4879            }
 4880
 04881            return (mainFilters, subFilters, overlayFilters);
 4882        }
 4883
 4884        /// <summary>
 4885        /// Gets the parameter of Intel/AMD VAAPI filter chain.
 4886        /// </summary>
 4887        /// <param name="state">Encoding state.</param>
 4888        /// <param name="options">Encoding options.</param>
 4889        /// <param name="vidEncoder">Video encoder to use.</param>
 4890        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4891        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiVidFilterChain(
 4892            EncodingJobInfo state,
 4893            EncodingOptions options,
 4894            string vidEncoder)
 4895        {
 04896            if (options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 4897            {
 04898                return (null, null, null);
 4899            }
 4900
 04901            var isLinux = OperatingSystem.IsLinux();
 04902            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04903            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04904            var isSwEncoder = !vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04905            var isVaapiFullSupported = isLinux && IsVaapiSupported(state) && IsVaapiFullSupported();
 04906            var isVaapiOclSupported = isVaapiFullSupported && IsOpenclFullSupported();
 04907            var isVaapiVkSupported = isVaapiFullSupported && IsVulkanFullSupported();
 4908
 4909            // legacy vaapi pipeline(copy-back)
 04910            if ((isSwDecoder && isSwEncoder)
 04911                || !isVaapiOclSupported
 04912                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4913            {
 04914                var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
 4915
 04916                if (!isSwEncoder)
 4917                {
 04918                    var newfilters = new List<string>();
 04919                    var noOverlay = swFilterChain.OverlayFilters.Count == 0;
 04920                    newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
 04921                    newfilters.Add("hwupload=derive_device=vaapi");
 4922
 04923                    var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
 04924                    var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
 04925                    return (mainFilters, swFilterChain.SubFilters, overlayFilters);
 4926                }
 4927
 04928                return swFilterChain;
 4929            }
 4930
 4931            // preferred vaapi + opencl filters pipeline
 04932            if (_mediaEncoder.IsVaapiDeviceInteliHD)
 4933            {
 4934                // Intel iHD path, with extra vpp tonemap and overlay support.
 04935                return GetIntelVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4936            }
 4937
 4938            // preferred vaapi + vulkan filters pipeline
 04939            if (_mediaEncoder.IsVaapiDeviceAmd
 04940                && isVaapiVkSupported
 04941                && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 04942                && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 4943            {
 4944                // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support.
 04945                return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4946            }
 4947
 4948            // Intel i965 and Amd legacy driver path, only featuring scale and deinterlace support.
 04949            return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4950        }
 4951
 4952        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVaapiFullVidFilt
 4953            EncodingJobInfo state,
 4954            EncodingOptions options,
 4955            string vidDecoder,
 4956            string vidEncoder)
 4957        {
 04958            var inW = state.VideoStream?.Width;
 04959            var inH = state.VideoStream?.Height;
 04960            var reqW = state.BaseRequest.Width;
 04961            var reqH = state.BaseRequest.Height;
 04962            var reqMaxW = state.BaseRequest.MaxWidth;
 04963            var reqMaxH = state.BaseRequest.MaxHeight;
 04964            var threeDFormat = state.MediaSource.Video3DFormat;
 4965
 04966            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04967            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04968            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04969            var isSwEncoder = !isVaapiEncoder;
 04970            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04971            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 4972
 04973            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04974            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04975            var doVaVppTonemap = isVaapiDecoder && IsIntelVppTonemapAvailable(state, options);
 04976            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04977            var doTonemap = doVaVppTonemap || doOclTonemap;
 04978            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4979
 04980            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04981            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04982            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04983            var hasAssSubs = hasSubs
 04984                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04985                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04986            var subW = state.SubtitleStream?.Width;
 04987            var subH = state.SubtitleStream?.Height;
 4988
 04989            var rotation = state.VideoStream?.Rotation ?? 0;
 04990            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04991            var doVaVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04992            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVaVppTranspose));
 04993            var swpInW = swapWAndH ? inH : inW;
 04994            var swpInH = swapWAndH ? inW : inH;
 4995
 4996            /* Make main filters for video stream */
 04997            var mainFilters = new List<string>();
 4998
 04999            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 5000
 05001            if (isSwDecoder)
 5002            {
 5003                // INPUT sw surface(memory)
 5004                // sw deint
 05005                if (doDeintH2645)
 5006                {
 05007                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05008                    mainFilters.Add(swDeintFilter);
 5009                }
 5010
 05011                var outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05012                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05013                if (isMjpegEncoder && !doOclTonemap)
 5014                {
 5015                    // sw decoder + hw mjpeg encoder
 05016                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5017                }
 5018
 5019                // sw scale
 05020                mainFilters.Add(swScaleFilter);
 05021                mainFilters.Add($"format={outFormat}");
 5022
 5023                // keep video at memory except ocl tonemap,
 5024                // since the overhead caused by hwupload >>> using sw filter.
 5025                // sw => hw
 05026                if (doOclTonemap)
 5027                {
 05028                    mainFilters.Add("hwupload=derive_device=opencl");
 5029                }
 5030            }
 05031            else if (isVaapiDecoder)
 5032            {
 05033                var isRext = IsVideoStreamHevcRext(state);
 5034
 5035                // INPUT vaapi surface(vram)
 5036                // hw deint
 05037                if (doDeintH2645)
 5038                {
 05039                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05040                    mainFilters.Add(deintFilter);
 5041                }
 5042
 5043                // hw transpose
 05044                if (doVaVppTranspose)
 5045                {
 05046                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 5047                }
 5048
 05049                var outFormat = doTonemap ? (isRext ? "p010" : string.Empty) : "nv12";
 05050                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, swpInW, swpInH, reqW, reqH, req
 5051
 05052                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5053                {
 05054                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05055                    hwScaleFilter += ":mode=hq";
 5056                }
 5057
 5058                // allocate extra pool sizes for vaapi vpp
 05059                if (!string.IsNullOrEmpty(hwScaleFilter))
 5060                {
 05061                    hwScaleFilter += ":extra_hw_frames=24";
 5062                }
 5063
 5064                // hw scale
 05065                mainFilters.Add(hwScaleFilter);
 5066            }
 5067
 5068            // vaapi vpp tonemap
 05069            if (doVaVppTonemap && isVaapiDecoder)
 5070            {
 05071                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 05072                mainFilters.Add(tonemapFilter);
 5073            }
 5074
 05075            if (doOclTonemap && isVaapiDecoder)
 5076            {
 5077                // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05078                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 5079            }
 5080
 5081            // ocl tonemap
 05082            if (doOclTonemap)
 5083            {
 05084                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05085                mainFilters.Add(tonemapFilter);
 5086            }
 5087
 05088            if (doOclTonemap && isVaInVaOut)
 5089            {
 5090                // OUTPUT vaapi(nv12) surface(vram)
 5091                // reverse-mapping via vaapi-opencl interop.
 05092                mainFilters.Add("hwmap=derive_device=vaapi:mode=write:reverse=1");
 05093                mainFilters.Add("format=vaapi");
 5094            }
 5095
 05096            var memoryOutput = false;
 05097            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 05098            var isHwmapNotUsable = isUploadForOclTonemap && isVaapiEncoder;
 05099            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap)
 5100            {
 05101                memoryOutput = true;
 5102
 5103                // OUTPUT nv12 surface(memory)
 5104                // prefer hwmap to hwdownload on opencl/vaapi.
 05105                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap=mode=read");
 05106                mainFilters.Add("format=nv12");
 5107            }
 5108
 5109            // OUTPUT nv12 surface(memory)
 05110            if (isSwDecoder && isVaapiEncoder)
 5111            {
 05112                memoryOutput = true;
 5113            }
 5114
 05115            if (memoryOutput)
 5116            {
 5117                // text subtitles
 05118                if (hasTextSubs)
 5119                {
 05120                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05121                    mainFilters.Add(textSubtitlesFilter);
 5122                }
 5123            }
 5124
 05125            if (memoryOutput && isVaapiEncoder)
 5126            {
 05127                if (!hasGraphicalSubs)
 5128                {
 05129                    mainFilters.Add("hwupload_vaapi");
 5130                }
 5131            }
 5132
 5133            /* Make sub and overlay filters for subtitle stream */
 05134            var subFilters = new List<string>();
 05135            var overlayFilters = new List<string>();
 05136            if (isVaInVaOut)
 5137            {
 05138                if (hasSubs)
 5139                {
 05140                    if (hasGraphicalSubs)
 5141                    {
 5142                        // overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead
 05143                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 05144                        subFilters.Add(subPreProcFilters);
 05145                        subFilters.Add("format=bgra");
 5146                    }
 05147                    else if (hasTextSubs)
 5148                    {
 05149                        var framerate = state.VideoStream?.RealFrameRate;
 05150                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5151
 05152                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 05153                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05154                        subFilters.Add(alphaSrcFilter);
 05155                        subFilters.Add("format=bgra");
 05156                        subFilters.Add(subTextSubtitlesFilter);
 5157                    }
 5158
 05159                    subFilters.Add("hwupload=derive_device=vaapi");
 5160
 05161                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 05162                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 05163                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 05164                        : string.Empty;
 05165                    var overlayVaapiFilter = string.Format(
 05166                        CultureInfo.InvariantCulture,
 05167                        "overlay_vaapi=eof_action=pass:repeatlast=0{0}",
 05168                        overlaySize);
 05169                    overlayFilters.Add(overlayVaapiFilter);
 5170                }
 5171            }
 05172            else if (memoryOutput)
 5173            {
 05174                if (hasGraphicalSubs)
 5175                {
 05176                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05177                    subFilters.Add(subPreProcFilters);
 05178                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5179
 05180                    if (isVaapiEncoder)
 5181                    {
 05182                        overlayFilters.Add("hwupload_vaapi");
 5183                    }
 5184                }
 5185            }
 5186
 05187            return (mainFilters, subFilters, overlayFilters);
 5188        }
 5189
 5190        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVaapiFullVidFilter
 5191            EncodingJobInfo state,
 5192            EncodingOptions options,
 5193            string vidDecoder,
 5194            string vidEncoder)
 5195        {
 05196            var inW = state.VideoStream?.Width;
 05197            var inH = state.VideoStream?.Height;
 05198            var reqW = state.BaseRequest.Width;
 05199            var reqH = state.BaseRequest.Height;
 05200            var reqMaxW = state.BaseRequest.MaxWidth;
 05201            var reqMaxH = state.BaseRequest.MaxHeight;
 05202            var threeDFormat = state.MediaSource.Video3DFormat;
 5203
 05204            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05205            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05206            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05207            var isSwEncoder = !isVaapiEncoder;
 05208            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5209
 05210            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05211            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05212            var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
 05213            var doDeintH2645 = doDeintH264 || doDeintHevc;
 5214
 05215            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05216            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05217            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05218            var hasAssSubs = hasSubs
 05219                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05220                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5221
 05222            var rotation = state.VideoStream?.Rotation ?? 0;
 05223            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05224            var doVkTranspose = isVaapiDecoder && !string.IsNullOrEmpty(transposeDir);
 05225            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVkTranspose));
 05226            var swpInW = swapWAndH ? inH : inW;
 05227            var swpInH = swapWAndH ? inW : inH;
 5228
 5229            /* Make main filters for video stream */
 05230            var mainFilters = new List<string>();
 5231
 05232            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doVkTonemap));
 5233
 05234            if (isSwDecoder)
 5235            {
 5236                // INPUT sw surface(memory)
 5237                // sw deint
 05238                if (doDeintH2645)
 5239                {
 05240                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05241                    mainFilters.Add(swDeintFilter);
 5242                }
 5243
 05244                if (doVkTonemap || hasSubs)
 5245                {
 5246                    // sw => hw
 05247                    mainFilters.Add("hwupload=derive_device=vulkan");
 05248                    mainFilters.Add("format=vulkan");
 5249                }
 5250                else
 5251                {
 5252                    // sw scale
 05253                    var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW,
 05254                    mainFilters.Add(swScaleFilter);
 05255                    mainFilters.Add("format=nv12");
 5256                }
 5257            }
 05258            else if (isVaapiDecoder)
 5259            {
 5260                // INPUT vaapi surface(vram)
 05261                if (doVkTranspose || doVkTonemap || hasSubs)
 5262                {
 5263                    // map from vaapi to vulkan/drm via interop (Polaris/gfx8+).
 05264                    if (_mediaEncoder.EncoderVersion >= _minFFmpegAlteredVaVkInterop)
 5265                    {
 05266                        if (doVkTranspose || !_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5267                        {
 5268                            // disable the indirect va-drm-vk mapping since it's no longer reliable.
 05269                            mainFilters.Add("hwmap=derive_device=drm");
 05270                            mainFilters.Add("format=drm_prime");
 05271                            mainFilters.Add("hwmap=derive_device=vulkan");
 05272                            mainFilters.Add("format=vulkan");
 5273
 5274                            // workaround for libplacebo using the imported vulkan frame on gfx8.
 05275                            if (!_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5276                            {
 05277                                mainFilters.Add("scale_vulkan");
 5278                            }
 5279                        }
 05280                        else if (doVkTonemap || hasSubs)
 5281                        {
 5282                            // non ad-hoc libplacebo also accepts drm_prime direct input.
 05283                            mainFilters.Add("hwmap=derive_device=drm");
 05284                            mainFilters.Add("format=drm_prime");
 5285                        }
 5286                    }
 5287                    else // legacy va-vk mapping that works only in jellyfin-ffmpeg6
 5288                    {
 05289                        mainFilters.Add("hwmap=derive_device=vulkan");
 05290                        mainFilters.Add("format=vulkan");
 5291                    }
 5292                }
 5293                else
 5294                {
 5295                    // hw deint
 05296                    if (doDeintH2645)
 5297                    {
 05298                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05299                        mainFilters.Add(deintFilter);
 5300                    }
 5301
 5302                    // hw scale
 05303                    var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", "nv12", false, inW, inH, reqW, reqH, reqMaxW,
 5304
 05305                    if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder && !doVkTonemap)
 5306                    {
 05307                        hwScaleFilter += ":out_range=pc:mode=hq";
 5308                    }
 5309
 05310                    mainFilters.Add(hwScaleFilter);
 5311                }
 5312            }
 5313
 5314            // vk transpose
 05315            if (doVkTranspose)
 5316            {
 05317                if (string.Equals(transposeDir, "reversal", StringComparison.OrdinalIgnoreCase))
 5318                {
 05319                    mainFilters.Add("flip_vulkan");
 5320                }
 5321                else
 5322                {
 05323                    mainFilters.Add($"transpose_vulkan=dir={transposeDir}");
 5324                }
 5325            }
 5326
 5327            // vk libplacebo
 05328            if (doVkTonemap || hasSubs)
 5329            {
 05330                var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, req
 05331                mainFilters.Add(libplaceboFilter);
 05332                mainFilters.Add("format=vulkan");
 5333            }
 5334
 05335            if (doVkTonemap && !hasSubs)
 5336            {
 5337                // OUTPUT vaapi(nv12) surface(vram)
 5338                // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05339                mainFilters.Add("hwmap=derive_device=vaapi");
 05340                mainFilters.Add("format=vaapi");
 5341
 5342                // clear the surf->meta_offset and output nv12
 05343                mainFilters.Add("scale_vaapi=format=nv12");
 5344
 5345                // hw deint
 05346                if (doDeintH2645)
 5347                {
 05348                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05349                    mainFilters.Add(deintFilter);
 5350                }
 5351            }
 5352
 05353            if (!hasSubs)
 5354            {
 5355                // OUTPUT nv12 surface(memory)
 05356                if (isSwEncoder && (doVkTonemap || isVaapiDecoder))
 5357                {
 05358                    mainFilters.Add("hwdownload");
 05359                    mainFilters.Add("format=nv12");
 5360                }
 5361
 05362                if (isSwDecoder && isVaapiEncoder && !doVkTonemap)
 5363                {
 05364                    mainFilters.Add("hwupload_vaapi");
 5365                }
 5366            }
 5367
 5368            /* Make sub and overlay filters for subtitle stream */
 05369            var subFilters = new List<string>();
 05370            var overlayFilters = new List<string>();
 05371            if (hasSubs)
 5372            {
 05373                if (hasGraphicalSubs)
 5374                {
 05375                    var subW = state.SubtitleStream?.Width;
 05376                    var subH = state.SubtitleStream?.Height;
 05377                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05378                    subFilters.Add(subPreProcFilters);
 05379                    subFilters.Add("format=bgra");
 5380                }
 05381                else if (hasTextSubs)
 5382                {
 05383                    var framerate = state.VideoStream?.RealFrameRate;
 05384                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5385
 05386                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05387                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05388                    subFilters.Add(alphaSrcFilter);
 05389                    subFilters.Add("format=bgra");
 05390                    subFilters.Add(subTextSubtitlesFilter);
 5391                }
 5392
 05393                subFilters.Add("hwupload=derive_device=vulkan");
 05394                subFilters.Add("format=vulkan");
 5395
 05396                overlayFilters.Add("overlay_vulkan=eof_action=pass:repeatlast=0");
 5397
 05398                if (isSwEncoder)
 5399                {
 5400                    // OUTPUT nv12 surface(memory)
 05401                    overlayFilters.Add("scale_vulkan=format=nv12");
 05402                    overlayFilters.Add("hwdownload");
 05403                    overlayFilters.Add("format=nv12");
 5404                }
 05405                else if (isVaapiEncoder)
 5406                {
 5407                    // OUTPUT vaapi(nv12) surface(vram)
 5408                    // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05409                    overlayFilters.Add("hwmap=derive_device=vaapi");
 05410                    overlayFilters.Add("format=vaapi");
 5411
 5412                    // clear the surf->meta_offset and output nv12
 05413                    overlayFilters.Add("scale_vaapi=format=nv12");
 5414
 5415                    // hw deint
 05416                    if (doDeintH2645)
 5417                    {
 05418                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05419                        overlayFilters.Add(deintFilter);
 5420                    }
 5421                }
 5422            }
 5423
 05424            return (mainFilters, subFilters, overlayFilters);
 5425        }
 5426
 5427        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiLimitedVidFilter
 5428            EncodingJobInfo state,
 5429            EncodingOptions options,
 5430            string vidDecoder,
 5431            string vidEncoder)
 5432        {
 05433            var inW = state.VideoStream?.Width;
 05434            var inH = state.VideoStream?.Height;
 05435            var reqW = state.BaseRequest.Width;
 05436            var reqH = state.BaseRequest.Height;
 05437            var reqMaxW = state.BaseRequest.MaxWidth;
 05438            var reqMaxH = state.BaseRequest.MaxHeight;
 05439            var threeDFormat = state.MediaSource.Video3DFormat;
 5440
 05441            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05442            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05443            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05444            var isSwEncoder = !isVaapiEncoder;
 05445            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05446            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 05447            var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965;
 05448            var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd;
 5449
 05450            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05451            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05452            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05453            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5454
 05455            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05456            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05457            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 5458
 05459            var rotation = state.VideoStream?.Rotation ?? 0;
 05460            var swapWAndH = Math.Abs(rotation) == 90 && isSwDecoder;
 05461            var swpInW = swapWAndH ? inH : inW;
 05462            var swpInH = swapWAndH ? inW : inH;
 5463
 5464            /* Make main filters for video stream */
 05465            var mainFilters = new List<string>();
 5466
 05467            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5468
 05469            var outFormat = string.Empty;
 05470            if (isSwDecoder)
 5471            {
 5472                // INPUT sw surface(memory)
 5473                // sw deint
 05474                if (doDeintH2645)
 5475                {
 05476                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05477                    mainFilters.Add(swDeintFilter);
 5478                }
 5479
 05480                outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05481                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05482                if (isMjpegEncoder && !doOclTonemap)
 5483                {
 5484                    // sw decoder + hw mjpeg encoder
 05485                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5486                }
 5487
 5488                // sw scale
 05489                mainFilters.Add(swScaleFilter);
 05490                mainFilters.Add("format=" + outFormat);
 5491
 5492                // keep video at memory except ocl tonemap,
 5493                // since the overhead caused by hwupload >>> using sw filter.
 5494                // sw => hw
 05495                if (doOclTonemap)
 5496                {
 05497                    mainFilters.Add("hwupload=derive_device=opencl");
 5498                }
 5499            }
 05500            else if (isVaapiDecoder)
 5501            {
 5502                // INPUT vaapi surface(vram)
 5503                // hw deint
 05504                if (doDeintH2645)
 5505                {
 05506                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05507                    mainFilters.Add(deintFilter);
 5508                }
 5509
 05510                outFormat = doOclTonemap ? string.Empty : "nv12";
 05511                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, inW, inH, reqW, reqH, reqMaxW, 
 5512
 05513                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5514                {
 05515                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05516                    hwScaleFilter += ":mode=hq";
 5517                }
 5518
 5519                // allocate extra pool sizes for vaapi vpp
 05520                if (!string.IsNullOrEmpty(hwScaleFilter))
 5521                {
 05522                    hwScaleFilter += ":extra_hw_frames=24";
 5523                }
 5524
 5525                // hw scale
 05526                mainFilters.Add(hwScaleFilter);
 5527            }
 5528
 05529            if (doOclTonemap && isVaapiDecoder)
 5530            {
 05531                if (isi965Driver)
 5532                {
 5533                    // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05534                    mainFilters.Add("hwmap=derive_device=opencl");
 5535                }
 5536                else
 5537                {
 05538                    mainFilters.Add("hwdownload");
 05539                    mainFilters.Add("format=p010le");
 05540                    mainFilters.Add("hwupload=derive_device=opencl");
 5541                }
 5542            }
 5543
 5544            // ocl tonemap
 05545            if (doOclTonemap)
 5546            {
 05547                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05548                mainFilters.Add(tonemapFilter);
 5549            }
 5550
 05551            if (doOclTonemap && isVaInVaOut)
 5552            {
 05553                if (isi965Driver)
 5554                {
 5555                    // OUTPUT vaapi(nv12) surface(vram)
 5556                    // reverse-mapping via vaapi-opencl interop.
 05557                    mainFilters.Add("hwmap=derive_device=vaapi:reverse=1");
 05558                    mainFilters.Add("format=vaapi");
 5559                }
 5560            }
 5561
 05562            var memoryOutput = false;
 05563            var isUploadForOclTonemap = doOclTonemap && (isSwDecoder || (isVaapiDecoder && !isi965Driver));
 05564            var isHwmapNotUsable = hasGraphicalSubs || isUploadForOclTonemap;
 05565            var isHwmapForSubs = hasSubs && isVaapiDecoder;
 05566            var isHwUnmapForTextSubs = hasTextSubs && isVaInVaOut && !isUploadForOclTonemap;
 05567            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap || isHwmapForSubs)
 5568            {
 05569                memoryOutput = true;
 5570
 5571                // OUTPUT nv12 surface(memory)
 5572                // prefer hwmap to hwdownload on opencl/vaapi.
 05573                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap");
 05574                mainFilters.Add("format=nv12");
 5575            }
 5576
 5577            // OUTPUT nv12 surface(memory)
 05578            if (isSwDecoder && isVaapiEncoder)
 5579            {
 05580                memoryOutput = true;
 5581            }
 5582
 05583            if (memoryOutput)
 5584            {
 5585                // text subtitles
 05586                if (hasTextSubs)
 5587                {
 05588                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05589                    mainFilters.Add(textSubtitlesFilter);
 5590                }
 5591            }
 5592
 05593            if (isHwUnmapForTextSubs)
 5594            {
 05595                mainFilters.Add("hwmap");
 05596                mainFilters.Add("format=vaapi");
 5597            }
 05598            else if (memoryOutput && isVaapiEncoder)
 5599            {
 05600                if (!hasGraphicalSubs)
 5601                {
 05602                    mainFilters.Add("hwupload_vaapi");
 5603                }
 5604            }
 5605
 5606            /* Make sub and overlay filters for subtitle stream */
 05607            var subFilters = new List<string>();
 05608            var overlayFilters = new List<string>();
 05609            if (memoryOutput)
 5610            {
 05611                if (hasGraphicalSubs)
 5612                {
 05613                    var subW = state.SubtitleStream?.Width;
 05614                    var subH = state.SubtitleStream?.Height;
 05615                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05616                    subFilters.Add(subPreProcFilters);
 05617                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5618
 05619                    if (isVaapiEncoder)
 5620                    {
 05621                        overlayFilters.Add("hwupload_vaapi");
 5622                    }
 5623                }
 5624            }
 5625
 05626            return (mainFilters, subFilters, overlayFilters);
 5627        }
 5628
 5629        /// <summary>
 5630        /// Gets the parameter of Apple VideoToolBox filter chain.
 5631        /// </summary>
 5632        /// <param name="state">Encoding state.</param>
 5633        /// <param name="options">Encoding options.</param>
 5634        /// <param name="vidEncoder">Video encoder to use.</param>
 5635        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5636        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFilterChain(
 5637            EncodingJobInfo state,
 5638            EncodingOptions options,
 5639            string vidEncoder)
 5640        {
 05641            if (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 5642            {
 05643                return (null, null, null);
 5644            }
 5645
 5646            // ReSharper disable once InconsistentNaming
 05647            var isMacOS = OperatingSystem.IsMacOS();
 05648            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05649            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05650            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05651            var isVtFullSupported = isMacOS && IsVideoToolboxFullSupported();
 5652
 5653            // legacy videotoolbox pipeline (disable hw filters)
 05654            if (!(isVtEncoder || isVtDecoder)
 05655                || !isVtFullSupported
 05656                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5657            {
 05658                return GetSwVidFilterChain(state, options, vidEncoder);
 5659            }
 5660
 5661            // preferred videotoolbox + metal filters pipeline
 05662            return GetAppleVidFiltersPreferred(state, options, vidDecoder, vidEncoder);
 5663        }
 5664
 5665        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFiltersPrefer
 5666            EncodingJobInfo state,
 5667            EncodingOptions options,
 5668            string vidDecoder,
 5669            string vidEncoder)
 5670        {
 05671            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05672            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05673            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5674
 05675            var inW = state.VideoStream?.Width;
 05676            var inH = state.VideoStream?.Height;
 05677            var reqW = state.BaseRequest.Width;
 05678            var reqH = state.BaseRequest.Height;
 05679            var reqMaxW = state.BaseRequest.MaxWidth;
 05680            var reqMaxH = state.BaseRequest.MaxHeight;
 05681            var threeDFormat = state.MediaSource.Video3DFormat;
 5682
 05683            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05684            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05685            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05686            var doVtTonemap = IsVideoToolboxTonemapAvailable(state, options);
 05687            var doMetalTonemap = !doVtTonemap && IsHwTonemapAvailable(state, options);
 05688            var usingHwSurface = isVtDecoder && (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface);
 5689
 05690            var rotation = state.VideoStream?.Rotation ?? 0;
 05691            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05692            var doVtTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_vt");
 05693            var swapWAndH = Math.Abs(rotation) == 90 && doVtTranspose;
 05694            var swpInW = swapWAndH ? inH : inW;
 05695            var swpInH = swapWAndH ? inW : inH;
 5696
 05697            var scaleFormat = string.Empty;
 5698            // Use P010 for Metal tone mapping, otherwise force an 8bit output.
 05699            if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase))
 5700            {
 05701                if (doMetalTonemap)
 5702                {
 05703                    if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 5704                    {
 05705                        scaleFormat = "p010le";
 5706                    }
 5707                }
 5708                else
 5709                {
 05710                    scaleFormat = "nv12";
 5711                }
 5712            }
 5713
 05714            var hwScaleFilter = GetHwScaleFilter("scale", "vt", scaleFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW,
 5715
 05716            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05717            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05718            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05719            var hasAssSubs = hasSubs
 05720                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05721                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5722
 5723            /* Make main filters for video stream */
 05724            var mainFilters = new List<string>();
 5725
 5726            // hw deint
 05727            if (doDeintH2645)
 5728            {
 05729                var deintFilter = GetHwDeinterlaceFilter(state, options, "videotoolbox");
 05730                mainFilters.Add(deintFilter);
 5731            }
 5732
 5733            // hw transpose
 05734            if (doVtTranspose)
 5735            {
 05736                mainFilters.Add($"transpose_vt=dir={transposeDir}");
 5737            }
 5738
 05739            if (doVtTonemap)
 5740            {
 5741                const string VtTonemapArgs = "color_matrix=bt709:color_primaries=bt709:color_transfer=bt709";
 5742
 5743                // scale_vt can handle scaling & tonemapping in one shot, just like vpp_qsv.
 05744                hwScaleFilter = string.IsNullOrEmpty(hwScaleFilter)
 05745                    ? "scale_vt=" + VtTonemapArgs
 05746                    : hwScaleFilter + ":" + VtTonemapArgs;
 5747            }
 5748
 5749            // hw scale & vt tonemap
 05750            mainFilters.Add(hwScaleFilter);
 5751
 5752            // Metal tonemap
 05753            if (doMetalTonemap)
 5754            {
 05755                var tonemapFilter = GetHwTonemapFilter(options, "videotoolbox", "nv12", isMjpegEncoder);
 05756                mainFilters.Add(tonemapFilter);
 5757            }
 5758
 5759            /* Make sub and overlay filters for subtitle stream */
 05760            var subFilters = new List<string>();
 05761            var overlayFilters = new List<string>();
 5762
 05763            if (hasSubs)
 5764            {
 05765                if (hasGraphicalSubs)
 5766                {
 05767                    var subW = state.SubtitleStream?.Width;
 05768                    var subH = state.SubtitleStream?.Height;
 05769                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05770                    subFilters.Add(subPreProcFilters);
 05771                    subFilters.Add("format=bgra");
 5772                }
 05773                else if (hasTextSubs)
 5774                {
 05775                    var framerate = state.VideoStream?.RealFrameRate;
 05776                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5777
 05778                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05779                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05780                    subFilters.Add(alphaSrcFilter);
 05781                    subFilters.Add("format=bgra");
 05782                    subFilters.Add(subTextSubtitlesFilter);
 5783                }
 5784
 05785                subFilters.Add("hwupload");
 05786                overlayFilters.Add("overlay_videotoolbox=eof_action=pass:repeatlast=0");
 5787            }
 5788
 05789            if (usingHwSurface)
 5790            {
 05791                if (!isVtEncoder)
 5792                {
 05793                    mainFilters.Add("hwdownload");
 05794                    mainFilters.Add("format=nv12");
 5795                }
 5796
 05797                return (mainFilters, subFilters, overlayFilters);
 5798            }
 5799
 5800            // For old jellyfin-ffmpeg that has broken hwsurface, add a hwupload
 05801            var needFiltering = mainFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05802                                subFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05803                                overlayFilters.Any(f => !string.IsNullOrEmpty(f));
 05804            if (needFiltering)
 5805            {
 5806                // INPUT videotoolbox/memory surface(vram/uma)
 5807                // this will pass-through automatically if in/out format matches.
 05808                mainFilters.Insert(0, "hwupload");
 05809                mainFilters.Insert(0, "format=nv12|p010le|videotoolbox_vld");
 5810
 05811                if (!isVtEncoder)
 5812                {
 05813                    mainFilters.Add("hwdownload");
 05814                    mainFilters.Add("format=nv12");
 5815                }
 5816            }
 5817
 05818            return (mainFilters, subFilters, overlayFilters);
 5819        }
 5820
 5821        /// <summary>
 5822        /// Gets the parameter of Rockchip RKMPP/RKRGA filter chain.
 5823        /// </summary>
 5824        /// <param name="state">Encoding state.</param>
 5825        /// <param name="options">Encoding options.</param>
 5826        /// <param name="vidEncoder">Video encoder to use.</param>
 5827        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5828        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFilterChain(
 5829            EncodingJobInfo state,
 5830            EncodingOptions options,
 5831            string vidEncoder)
 5832        {
 05833            if (options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 5834            {
 05835                return (null, null, null);
 5836            }
 5837
 05838            var isLinux = OperatingSystem.IsLinux();
 05839            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05840            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05841            var isSwEncoder = !vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05842            var isRkmppOclSupported = isLinux && IsRkmppFullSupported() && IsOpenclFullSupported();
 5843
 05844            if ((isSwDecoder && isSwEncoder)
 05845                || !isRkmppOclSupported
 05846                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5847            {
 05848                return GetSwVidFilterChain(state, options, vidEncoder);
 5849            }
 5850
 5851            // preferred rkmpp + rkrga + opencl filters pipeline
 05852            if (isRkmppOclSupported)
 5853            {
 05854                return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5855            }
 5856
 05857            return (null, null, null);
 5858        }
 5859
 5860        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFiltersPrefer
 5861            EncodingJobInfo state,
 5862            EncodingOptions options,
 5863            string vidDecoder,
 5864            string vidEncoder)
 5865        {
 05866            var inW = state.VideoStream?.Width;
 05867            var inH = state.VideoStream?.Height;
 05868            var reqW = state.BaseRequest.Width;
 05869            var reqH = state.BaseRequest.Height;
 05870            var reqMaxW = state.BaseRequest.MaxWidth;
 05871            var reqMaxH = state.BaseRequest.MaxHeight;
 05872            var threeDFormat = state.MediaSource.Video3DFormat;
 5873
 05874            var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05875            var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05876            var isSwDecoder = !isRkmppDecoder;
 05877            var isSwEncoder = !isRkmppEncoder;
 05878            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05879            var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder;
 05880            var isEncoderSupportAfbc = isRkmppEncoder
 05881                && (vidEncoder.Contains("h264", StringComparison.OrdinalIgnoreCase)
 05882                    || vidEncoder.Contains("hevc", StringComparison.OrdinalIgnoreCase));
 5883
 05884            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05885            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05886            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05887            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5888
 05889            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05890            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05891            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05892            var hasAssSubs = hasSubs
 05893                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05894                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 05895            var subW = state.SubtitleStream?.Width;
 05896            var subH = state.SubtitleStream?.Height;
 5897
 05898            var rotation = state.VideoStream?.Rotation ?? 0;
 05899            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05900            var doRkVppTranspose = !string.IsNullOrEmpty(transposeDir);
 05901            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isRkmppDecoder && doRkVppTranspose));
 05902            var swpInW = swapWAndH ? inH : inW;
 05903            var swpInH = swapWAndH ? inW : inH;
 5904
 5905            /* Make main filters for video stream */
 05906            var mainFilters = new List<string>();
 5907
 05908            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5909
 05910            if (isSwDecoder)
 5911            {
 5912                // INPUT sw surface(memory)
 5913                // sw deint
 05914                if (doDeintH2645)
 5915                {
 05916                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05917                    mainFilters.Add(swDeintFilter);
 5918                }
 5919
 05920                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 05921                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05922                if (isMjpegEncoder && !doOclTonemap)
 5923                {
 5924                    // sw decoder + hw mjpeg encoder
 05925                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5926                }
 5927
 05928                if (!string.IsNullOrEmpty(swScaleFilter))
 5929                {
 05930                    swScaleFilter += ":flags=fast_bilinear";
 5931                }
 5932
 5933                // sw scale
 05934                mainFilters.Add(swScaleFilter);
 05935                mainFilters.Add($"format={outFormat}");
 5936
 5937                // keep video at memory except ocl tonemap,
 5938                // since the overhead caused by hwupload >>> using sw filter.
 5939                // sw => hw
 05940                if (doOclTonemap)
 5941                {
 05942                    mainFilters.Add("hwupload=derive_device=opencl");
 5943                }
 5944            }
 05945            else if (isRkmppDecoder)
 5946            {
 5947                // INPUT rkmpp/drm surface(gem/dma-heap)
 5948
 05949                var isFullAfbcPipeline = isEncoderSupportAfbc && isDrmInDrmOut && !doOclTonemap;
 05950                var swapOutputWandH = doRkVppTranspose && swapWAndH;
 05951                var outFormat = doOclTonemap ? "p010" : "nv12";
 05952                var hwScaleFilter = GetHwScaleFilter("vpp", "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, r
 05953                var doScaling = !string.IsNullOrEmpty(GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, sw
 5954
 05955                if (!hasSubs
 05956                     || doRkVppTranspose
 05957                     || !isFullAfbcPipeline
 05958                     || doScaling)
 5959                {
 05960                    var isScaleRatioSupported = IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 5961
 5962                    // RGA3 hardware only support (1/8 ~ 8) scaling in each blit operation,
 5963                    // but in Trickplay there's a case: (3840/320 == 12), enable 2pass for it
 05964                    if (doScaling && !isScaleRatioSupported)
 5965                    {
 5966                        // Vendor provided BSP kernel has an RGA driver bug that causes the output to be corrupted for P
 5967                        // Use NV15 instead of P010 to avoid the issue.
 5968                        // SDR inputs are using BGRA formats already which is not affected.
 05969                        var intermediateFormat = doOclTonemap ? "nv15" : (isMjpegEncoder ? "bgra" : outFormat);
 05970                        var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={intermediateFormat}:force_o
 05971                        mainFilters.Add(hwScaleFilterFirstPass);
 5972                    }
 5973
 5974                    // The RKMPP MJPEG encoder on some newer chip models no longer supports RGB input.
 5975                    // Use 2pass here to enable RGA output of full-range YUV in the 2nd pass.
 05976                    if (isMjpegEncoder && !doOclTonemap && ((doScaling && isScaleRatioSupported) || !doScaling))
 5977                    {
 05978                        var hwScaleFilterFirstPass = "vpp_rkrga=format=bgra:afbc=1";
 05979                        mainFilters.Add(hwScaleFilterFirstPass);
 5980                    }
 5981
 05982                    if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose)
 5983                    {
 05984                        hwScaleFilter += $":transpose={transposeDir}";
 5985                    }
 5986
 5987                    // try enabling AFBC to save DDR bandwidth
 05988                    if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline)
 5989                    {
 05990                        hwScaleFilter += ":afbc=1";
 5991                    }
 5992
 5993                    // hw transpose & scale
 05994                    mainFilters.Add(hwScaleFilter);
 5995                }
 5996            }
 5997
 05998            if (doOclTonemap && isRkmppDecoder)
 5999            {
 6000                // map from rkmpp/drm to opencl via drm-opencl interop.
 06001                mainFilters.Add("hwmap=derive_device=opencl");
 6002            }
 6003
 6004            // ocl tonemap
 06005            if (doOclTonemap)
 6006            {
 06007                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 06008                mainFilters.Add(tonemapFilter);
 6009            }
 6010
 06011            var memoryOutput = false;
 06012            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 06013            if ((isRkmppDecoder && isSwEncoder) || isUploadForOclTonemap)
 6014            {
 06015                memoryOutput = true;
 6016
 6017                // OUTPUT nv12 surface(memory)
 06018                mainFilters.Add("hwdownload");
 06019                mainFilters.Add("format=nv12");
 6020            }
 6021
 6022            // OUTPUT nv12 surface(memory)
 06023            if (isSwDecoder && isRkmppEncoder)
 6024            {
 06025                memoryOutput = true;
 6026            }
 6027
 06028            if (memoryOutput)
 6029            {
 6030                // text subtitles
 06031                if (hasTextSubs)
 6032                {
 06033                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 06034                    mainFilters.Add(textSubtitlesFilter);
 6035                }
 6036            }
 6037
 06038            if (isDrmInDrmOut)
 6039            {
 06040                if (doOclTonemap)
 6041                {
 6042                    // OUTPUT drm(nv12) surface(gem/dma-heap)
 6043                    // reverse-mapping via drm-opencl interop.
 06044                    mainFilters.Add("hwmap=derive_device=rkmpp:reverse=1");
 06045                    mainFilters.Add("format=drm_prime");
 6046                }
 6047            }
 6048
 6049            /* Make sub and overlay filters for subtitle stream */
 06050            var subFilters = new List<string>();
 06051            var overlayFilters = new List<string>();
 06052            if (isDrmInDrmOut)
 6053            {
 06054                if (hasSubs)
 6055                {
 06056                    var subMaxH = 1080;
 06057                    if (hasGraphicalSubs)
 6058                    {
 06059                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 06060                        subFilters.Add(subPreProcFilters);
 06061                        subFilters.Add("format=bgra");
 6062                    }
 06063                    else if (hasTextSubs)
 6064                    {
 06065                        var framerate = state.VideoStream?.RealFrameRate;
 06066                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 6067
 6068                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 06069                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, subMaxH, subF
 06070                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 06071                        subFilters.Add(alphaSrcFilter);
 06072                        subFilters.Add("format=bgra");
 06073                        subFilters.Add(subTextSubtitlesFilter);
 6074                    }
 6075
 06076                    subFilters.Add("hwupload=derive_device=rkmpp");
 6077
 6078                    // offload 1080p+ subtitles swscale upscaling from CPU to RGA
 06079                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 06080                    if (overlayW.HasValue && overlayH.HasValue && overlayH.Value > subMaxH)
 6081                    {
 06082                        subFilters.Add($"vpp_rkrga=w={overlayW.Value}:h={overlayH.Value}:format=bgra:afbc=1");
 6083                    }
 6084
 6085                    // try enabling AFBC to save DDR bandwidth
 06086                    var hwOverlayFilter = "overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12";
 06087                    if (isEncoderSupportAfbc)
 6088                    {
 06089                        hwOverlayFilter += ":afbc=1";
 6090                    }
 6091
 06092                    overlayFilters.Add(hwOverlayFilter);
 6093                }
 6094            }
 06095            else if (memoryOutput)
 6096            {
 06097                if (hasGraphicalSubs)
 6098                {
 06099                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 06100                    subFilters.Add(subPreProcFilters);
 06101                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 6102                }
 6103            }
 6104
 06105            return (mainFilters, subFilters, overlayFilters);
 6106        }
 6107
 6108        /// <summary>
 6109        /// Gets the parameter of video processing filters.
 6110        /// </summary>
 6111        /// <param name="state">Encoding state.</param>
 6112        /// <param name="options">Encoding options.</param>
 6113        /// <param name="outputVideoCodec">Video codec to use.</param>
 6114        /// <returns>The video processing filters parameter.</returns>
 6115        public string GetVideoProcessingFilterParam(
 6116            EncodingJobInfo state,
 6117            EncodingOptions options,
 6118            string outputVideoCodec)
 6119        {
 06120            var videoStream = state.VideoStream;
 06121            if (videoStream is null)
 6122            {
 06123                return string.Empty;
 6124            }
 6125
 06126            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 06127            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 06128            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 6129
 6130            List<string> mainFilters;
 6131            List<string> subFilters;
 6132            List<string> overlayFilters;
 6133
 06134            (mainFilters, subFilters, overlayFilters) = options.HardwareAccelerationType switch
 06135            {
 06136                HardwareAccelerationType.vaapi => GetVaapiVidFilterChain(state, options, outputVideoCodec),
 06137                HardwareAccelerationType.amf => GetAmdVidFilterChain(state, options, outputVideoCodec),
 06138                HardwareAccelerationType.qsv => GetIntelVidFilterChain(state, options, outputVideoCodec),
 06139                HardwareAccelerationType.nvenc => GetNvidiaVidFilterChain(state, options, outputVideoCodec),
 06140                HardwareAccelerationType.videotoolbox => GetAppleVidFilterChain(state, options, outputVideoCodec),
 06141                HardwareAccelerationType.rkmpp => GetRkmppVidFilterChain(state, options, outputVideoCodec),
 06142                _ => GetSwVidFilterChain(state, options, outputVideoCodec),
 06143            };
 6144
 06145            mainFilters?.RemoveAll(string.IsNullOrEmpty);
 06146            subFilters?.RemoveAll(string.IsNullOrEmpty);
 06147            overlayFilters?.RemoveAll(string.IsNullOrEmpty);
 6148
 06149            var framerate = GetFramerateParam(state);
 06150            if (framerate.HasValue)
 6151            {
 06152                mainFilters.Insert(0, string.Format(
 06153                    CultureInfo.InvariantCulture,
 06154                    "fps={0}",
 06155                    framerate.Value));
 6156            }
 6157
 06158            var mainStr = string.Empty;
 06159            if (mainFilters?.Count > 0)
 6160            {
 06161                mainStr = string.Format(
 06162                    CultureInfo.InvariantCulture,
 06163                    "{0}",
 06164                    string.Join(',', mainFilters));
 6165            }
 6166
 06167            if (overlayFilters?.Count == 0)
 6168            {
 6169                // -vf "scale..."
 06170                return string.IsNullOrEmpty(mainStr) ? string.Empty : " -vf \"" + mainStr + "\"";
 6171            }
 6172
 06173            if (overlayFilters?.Count > 0
 06174                && subFilters?.Count > 0
 06175                && state.SubtitleStream is not null)
 6176            {
 6177                // overlay graphical/text subtitles
 06178                var subStr = string.Format(
 06179                        CultureInfo.InvariantCulture,
 06180                        "{0}",
 06181                        string.Join(',', subFilters));
 6182
 06183                var overlayStr = string.Format(
 06184                        CultureInfo.InvariantCulture,
 06185                        "{0}",
 06186                        string.Join(',', overlayFilters));
 6187
 06188                var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
 06189                var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 06190                var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 6191
 06192                if (hasSubs)
 6193                {
 6194                    // -filter_complex "[0:s]scale=s[sub]..."
 06195                    var filterStr = string.IsNullOrEmpty(mainStr)
 06196                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]{5}\""
 06197                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6198
 06199                    if (hasTextSubs)
 6200                    {
 06201                        filterStr = string.IsNullOrEmpty(mainStr)
 06202                            ? " -filter_complex \"{4}[sub];[0:{2}][sub]{5}\""
 06203                            : " -filter_complex \"{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6204                    }
 6205
 06206                    return string.Format(
 06207                        CultureInfo.InvariantCulture,
 06208                        filterStr,
 06209                        mapPrefix,
 06210                        subtitleStreamIndex,
 06211                        videoStreamIndex,
 06212                        mainStr,
 06213                        subStr,
 06214                        overlayStr);
 6215                }
 6216            }
 6217
 06218            return string.Empty;
 6219        }
 6220
 6221        public string GetOverwriteColorPropertiesParam(EncodingJobInfo state, bool isTonemapAvailable)
 6222        {
 06223            if (isTonemapAvailable)
 6224            {
 06225                return GetInputHdrParam(state.VideoStream?.ColorTransfer);
 6226            }
 6227
 06228            return GetOutputSdrParam(null);
 6229        }
 6230
 6231        public string GetInputHdrParam(string colorTransfer)
 6232        {
 06233            if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
 6234            {
 6235                // HLG
 06236                return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
 6237            }
 6238
 6239            // HDR10
 06240            return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc";
 6241        }
 6242
 6243        public string GetOutputSdrParam(string tonemappingRange)
 6244        {
 6245            // SDR
 06246            if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
 6247            {
 06248                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
 6249            }
 6250
 06251            if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
 6252            {
 06253                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc";
 6254            }
 6255
 06256            return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
 6257        }
 6258
 6259        public static int GetVideoColorBitDepth(EncodingJobInfo state)
 6260        {
 06261            var videoStream = state.VideoStream;
 06262            if (videoStream is not null)
 6263            {
 06264                if (videoStream.BitDepth.HasValue)
 6265                {
 06266                    return videoStream.BitDepth.Value;
 6267                }
 6268
 06269                if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
 06270                    || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
 06271                    || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 06272                    || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
 6273                {
 06274                    return 8;
 6275                }
 6276
 06277                if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 06278                    || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 06279                    || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
 6280                {
 06281                    return 10;
 6282                }
 6283
 06284                if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 06285                    || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 06286                    || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
 6287                {
 06288                    return 12;
 6289                }
 6290
 06291                return 8;
 6292            }
 6293
 06294            return 0;
 6295        }
 6296
 6297        /// <summary>
 6298        /// Gets the ffmpeg option string for the hardware accelerated video decoder.
 6299        /// </summary>
 6300        /// <param name="state">The encoding job info.</param>
 6301        /// <param name="options">The encoding options.</param>
 6302        /// <returns>The option string or null if none available.</returns>
 6303        protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
 6304        {
 06305            var videoStream = state.VideoStream;
 06306            var mediaSource = state.MediaSource;
 06307            if (videoStream is null || mediaSource is null)
 6308            {
 06309                return null;
 6310            }
 6311
 6312            // HWA decoders can handle both video files and video folders.
 06313            var videoType = state.VideoType;
 06314            if (videoType != VideoType.VideoFile
 06315                && videoType != VideoType.Iso
 06316                && videoType != VideoType.Dvd
 06317                && videoType != VideoType.BluRay)
 6318            {
 06319                return null;
 6320            }
 6321
 06322            if (IsCopyCodec(state.OutputVideoCodec))
 6323            {
 06324                return null;
 6325            }
 6326
 06327            var hardwareAccelerationType = options.HardwareAccelerationType;
 6328
 06329            if (!string.IsNullOrEmpty(videoStream.Codec) && hardwareAccelerationType != HardwareAccelerationType.none)
 6330            {
 06331                var bitDepth = GetVideoColorBitDepth(state);
 6332
 6333                // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support for most platforms
 06334                if (bitDepth == 10
 06335                    && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06336                         || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
 06337                         || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)
 06338                         || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)))
 6339                {
 6340                    // RKMPP has H.264 Hi10P decoder
 06341                    bool hasHardwareHi10P = hardwareAccelerationType == HardwareAccelerationType.rkmpp;
 6342
 6343                    // VideoToolbox on Apple Silicon has H.264 Hi10P mode enabled after macOS 14.6
 06344                    if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox)
 6345                    {
 06346                        var ver = Environment.OSVersion.Version;
 06347                        var arch = RuntimeInformation.OSArchitecture;
 06348                        if (arch.Equals(Architecture.Arm64) && ver >= new Version(14, 6))
 6349                        {
 06350                            hasHardwareHi10P = true;
 6351                        }
 6352                    }
 6353
 06354                    if (!hasHardwareHi10P
 06355                        && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6356                    {
 06357                        return null;
 6358                    }
 6359                }
 6360
 6361                // Block unsupported H.264 Hi422P and Hi444PP profiles, which can be encoded with 4:2:0 pixel format
 06362                if (string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6363                {
 06364                    if (videoStream.Profile.Contains("4:2:2", StringComparison.OrdinalIgnoreCase)
 06365                        || videoStream.Profile.Contains("4:4:4", StringComparison.OrdinalIgnoreCase))
 6366                    {
 6367                        // VideoToolbox on Apple Silicon has H.264 Hi444PP and theoretically also has Hi422P
 06368                        if (!(hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06369                              && RuntimeInformation.OSArchitecture.Equals(Architecture.Arm64)))
 6370                        {
 06371                            return null;
 6372                        }
 6373                    }
 6374                }
 6375
 06376                var decoder = hardwareAccelerationType switch
 06377                {
 06378                    HardwareAccelerationType.vaapi => GetVaapiVidDecoder(state, options, videoStream, bitDepth),
 06379                    HardwareAccelerationType.amf => GetAmfVidDecoder(state, options, videoStream, bitDepth),
 06380                    HardwareAccelerationType.qsv => GetQsvHwVidDecoder(state, options, videoStream, bitDepth),
 06381                    HardwareAccelerationType.nvenc => GetNvdecVidDecoder(state, options, videoStream, bitDepth),
 06382                    HardwareAccelerationType.videotoolbox => GetVideotoolboxVidDecoder(state, options, videoStream, bitD
 06383                    HardwareAccelerationType.rkmpp => GetRkmppVidDecoder(state, options, videoStream, bitDepth),
 06384                    _ => string.Empty
 06385                };
 6386
 06387                if (!string.IsNullOrEmpty(decoder))
 6388                {
 06389                    return decoder;
 6390                }
 6391            }
 6392
 6393            // leave blank so ffmpeg will decide
 06394            return null;
 6395        }
 6396
 6397        /// <summary>
 6398        /// Gets a hw decoder name.
 6399        /// </summary>
 6400        /// <param name="options">Encoding options.</param>
 6401        /// <param name="decoderPrefix">Decoder prefix.</param>
 6402        /// <param name="decoderSuffix">Decoder suffix.</param>
 6403        /// <param name="videoCodec">Video codec to use.</param>
 6404        /// <param name="bitDepth">Video color bit depth.</param>
 6405        /// <returns>Hardware decoder name.</returns>
 6406        public string GetHwDecoderName(EncodingOptions options, string decoderPrefix, string decoderSuffix, string video
 6407        {
 06408            if (string.IsNullOrEmpty(decoderPrefix) || string.IsNullOrEmpty(decoderSuffix))
 6409            {
 06410                return null;
 6411            }
 6412
 06413            var decoderName = decoderPrefix + '_' + decoderSuffix;
 6414
 06415            var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains
 6416
 6417            // VideoToolbox decoders have built-in SW fallback
 06418            if (bitDepth == 10
 06419                && isCodecAvailable
 06420                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6421            {
 06422                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06423                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
 06424                    && !options.EnableDecodingColorDepth10Hevc)
 6425                {
 06426                    return null;
 6427                }
 6428
 06429                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06430                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06431                    && !options.EnableDecodingColorDepth10Vp9)
 6432                {
 06433                    return null;
 6434                }
 6435            }
 6436
 06437            if (string.Equals(decoderSuffix, "cuvid", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdec
 6438            {
 06439                return null;
 6440            }
 6441
 06442            if (string.Equals(decoderSuffix, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwD
 6443            {
 06444                return null;
 6445            }
 6446
 06447            if (string.Equals(decoderSuffix, "rkmpp", StringComparison.OrdinalIgnoreCase))
 6448            {
 06449                return null;
 6450            }
 6451
 06452            return isCodecAvailable ? (" -c:v " + decoderName) : null;
 6453        }
 6454
 6455        /// <summary>
 6456        /// Gets a hwaccel type to use as a hardware decoder depending on the system.
 6457        /// </summary>
 6458        /// <param name="state">Encoding state.</param>
 6459        /// <param name="options">Encoding options.</param>
 6460        /// <param name="videoCodec">Video codec to use.</param>
 6461        /// <param name="bitDepth">Video color bit depth.</param>
 6462        /// <param name="outputHwSurface">Specifies if output hw surface.</param>
 6463        /// <returns>Hardware accelerator type.</returns>
 6464        public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, int bitDepth, bo
 6465        {
 06466            var isWindows = OperatingSystem.IsWindows();
 06467            var isLinux = OperatingSystem.IsLinux();
 06468            var isMacOS = OperatingSystem.IsMacOS();
 06469            var isD3d11Supported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va");
 06470            var isVaapiSupported = isLinux && IsVaapiSupported(state);
 06471            var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported();
 06472            var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv");
 06473            var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
 06474            var isRkmppSupported = isLinux && IsRkmppFullSupported();
 06475            var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCas
 06476            var hardwareAccelerationType = options.HardwareAccelerationType;
 6477
 06478            var ffmpegVersion = _mediaEncoder.EncoderVersion;
 6479
 6480            // Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
 06481            var isAv1 = ffmpegVersion < _minFFmpegImplicitHwaccel
 06482                && string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
 6483
 6484            // Allow profile mismatch if decoding H.264 baseline with d3d11va and vaapi hwaccels.
 06485            var profileMismatch = string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
 06486                && string.Equals(state.VideoStream?.Profile, "baseline", StringComparison.OrdinalIgnoreCase);
 6487
 6488            // Disable the extra internal copy in nvdec. We already handle it in filter chain.
 06489            var nvdecNoInternalCopy = ffmpegVersion >= _minFFmpegHwaUnsafeOutput;
 6490
 6491            // Strip the display rotation side data from the transposed fmp4 output stream.
 06492            var stripRotationData = (state.VideoStream?.Rotation ?? 0) != 0
 06493                && ffmpegVersion >= _minFFmpegDisplayRotationOption;
 06494            var stripRotationDataArgs = stripRotationData ? " -display_rotation 0" : string.Empty;
 6495
 6496            // VideoToolbox decoders have built-in SW fallback
 06497            if (isCodecAvailable
 06498                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6499            {
 06500                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06501                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase))
 6502                {
 06503                    if (IsVideoStreamHevcRext(state))
 6504                    {
 06505                        if (bitDepth <= 10 && !options.EnableDecodingColorDepth10HevcRext)
 6506                        {
 06507                            return null;
 6508                        }
 6509
 06510                        if (bitDepth == 12 && !options.EnableDecodingColorDepth12HevcRext)
 6511                        {
 06512                            return null;
 6513                        }
 6514
 06515                        if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06516                            && !_mediaEncoder.IsVaapiDeviceInteliHD)
 6517                        {
 06518                            return null;
 6519                        }
 6520                    }
 06521                    else if (bitDepth == 10 && !options.EnableDecodingColorDepth10Hevc)
 6522                    {
 06523                        return null;
 6524                    }
 6525                }
 6526
 06527                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06528                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06529                    && bitDepth == 10
 06530                    && !options.EnableDecodingColorDepth10Vp9)
 6531                {
 06532                    return null;
 6533                }
 6534            }
 6535
 6536            // Intel qsv/d3d11va/vaapi
 06537            if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 6538            {
 06539                if (options.PreferSystemNativeHwDecoder)
 6540                {
 06541                    if (isVaapiSupported && isCodecAvailable)
 6542                    {
 06543                        return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + st
 06544                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " 
 6545                    }
 6546
 06547                    if (isD3d11Supported && isCodecAvailable)
 6548                    {
 06549                        return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + 
 06550                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 
 6551                    }
 6552                }
 6553                else
 6554                {
 06555                    if (isQsvSupported && isCodecAvailable)
 6556                    {
 06557                        return " -hwaccel qsv" + (outputHwSurface ? " -hwaccel_output_format qsv -noautorotate" + stripR
 6558                    }
 6559                }
 6560            }
 6561
 6562            // Nvidia cuda
 06563            if (hardwareAccelerationType == HardwareAccelerationType.nvenc)
 6564            {
 06565                if (isCudaSupported && isCodecAvailable)
 6566                {
 06567                    if (options.EnableEnhancedNvdecDecoder)
 6568                    {
 6569                        // set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support.
 06570                        return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stri
 06571                            + (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + 
 6572                    }
 6573
 6574                    // cuvid decoder doesn't have threading issue.
 06575                    return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stripRot
 6576                }
 6577            }
 6578
 6579            // Amd d3d11va
 06580            if (hardwareAccelerationType == HardwareAccelerationType.amf)
 6581            {
 06582                if (isD3d11Supported && isCodecAvailable)
 6583                {
 06584                    return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + stri
 06585                        + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 2" +
 6586                }
 6587            }
 6588
 6589            // Vaapi
 06590            if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06591                && isVaapiSupported
 06592                && isCodecAvailable)
 6593            {
 06594                return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + stripRotat
 06595                    + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1
 6596            }
 6597
 6598            // Apple videotoolbox
 06599            if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06600                && isVideotoolboxSupported
 06601                && isCodecAvailable)
 6602            {
 06603                return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string
 6604            }
 6605
 6606            // Rockchip rkmpp
 06607            if (hardwareAccelerationType == HardwareAccelerationType.rkmpp
 06608                && isRkmppSupported
 06609                && isCodecAvailable)
 6610            {
 06611                return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime -noautorotate" + stripR
 6612            }
 6613
 06614            return null;
 6615        }
 6616
 6617        public string GetQsvHwVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6618        {
 06619            var isWindows = OperatingSystem.IsWindows();
 06620            var isLinux = OperatingSystem.IsLinux();
 6621
 06622            if ((!isWindows && !isLinux)
 06623                || options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 6624            {
 06625                return null;
 6626            }
 6627
 06628            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 06629            var isIntelDx11OclSupported = isWindows
 06630                && _mediaEncoder.SupportsHwaccel("d3d11va")
 06631                && isQsvOclSupported;
 06632            var isIntelVaapiOclSupported = isLinux
 06633                && IsVaapiSupported(state)
 06634                && isQsvOclSupported;
 06635            var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
 06636                && _mediaEncoder.SupportsFilter("alphasrc");
 6637
 06638            var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06639                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06640            var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 06641            var is8_10_12bitSwFormatsQsv = is8_10bitSwFormatsQsv
 06642                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06643                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06644                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06645                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06646                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06647                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06648                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6649            // TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
 6650
 06651            if (is8bitSwFormatsQsv)
 6652            {
 06653                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06654                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6655                {
 06656                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6657                }
 6658
 06659                if (string.Equals(videoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase))
 6660                {
 06661                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6662                }
 6663
 06664                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6665                {
 06666                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6667                }
 6668
 06669                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6670                {
 06671                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6672                }
 6673            }
 6674
 06675            if (is8_10bitSwFormatsQsv)
 6676            {
 06677                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6678                {
 06679                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6680                }
 6681
 06682                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 6683                {
 06684                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6685                }
 6686            }
 6687
 06688            if (is8_10_12bitSwFormatsQsv)
 6689            {
 06690                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06691                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6692                {
 06693                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6694                }
 6695            }
 6696
 06697            return null;
 6698        }
 6699
 6700        public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6701        {
 06702            if ((!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
 06703                || options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 6704            {
 06705                return null;
 6706            }
 6707
 06708            var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
 06709            var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06710                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06711            var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06712            var is8_10_12bitSwFormatsNvdec = is8_10bitSwFormatsNvdec
 06713                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06714                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06715                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06716                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6717            // TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
 6718
 06719            if (is8bitSwFormatsNvdec)
 6720            {
 06721                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06722                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6723                {
 06724                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6725                }
 6726
 06727                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6728                {
 06729                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6730                }
 6731
 06732                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6733                {
 06734                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6735                }
 6736
 06737                if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6738                {
 06739                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpe
 6740                }
 6741
 06742                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6743                {
 06744                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6745                }
 6746            }
 6747
 06748            if (is8_10bitSwFormatsNvdec)
 6749            {
 06750                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6751                {
 06752                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6753                }
 6754
 06755                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6756                {
 06757                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6758                }
 6759            }
 6760
 06761            if (is8_10_12bitSwFormatsNvdec)
 6762            {
 06763                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06764                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6765                {
 06766                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6767                }
 6768            }
 6769
 06770            return null;
 6771        }
 6772
 6773        public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitD
 6774        {
 06775            if (!OperatingSystem.IsWindows()
 06776                || options.HardwareAccelerationType != HardwareAccelerationType.amf)
 6777            {
 06778                return null;
 6779            }
 6780
 06781            var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
 06782                && IsOpenclFullSupported()
 06783                && _mediaEncoder.SupportsFilter("alphasrc");
 06784            var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06785                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06786            var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 6787
 06788            if (is8bitSwFormatsAmf)
 6789            {
 06790                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06791                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6792                {
 06793                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6794                }
 6795
 06796                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6797                {
 06798                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6799                }
 6800
 06801                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6802                {
 06803                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6804                }
 6805            }
 6806
 06807            if (is8_10bitSwFormatsAmf)
 6808            {
 06809                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06810                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6811                {
 06812                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6813                }
 6814
 06815                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6816                {
 06817                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6818                }
 6819
 06820                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6821                {
 06822                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6823                }
 6824            }
 6825
 06826            return null;
 6827        }
 6828
 6829        public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6830        {
 06831            if (!OperatingSystem.IsLinux()
 06832                || options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 6833            {
 06834                return null;
 6835            }
 6836
 06837            var hwSurface = IsVaapiSupported(state)
 06838                && IsVaapiFullSupported()
 06839                && IsOpenclFullSupported()
 06840                && _mediaEncoder.SupportsFilter("alphasrc");
 06841            var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06842                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06843            var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06844            var is8_10_12bitSwFormatsVaapi = is8_10bitSwFormatsVaapi
 06845                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06846                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06847                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06848                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06849                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06850                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06851                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6852
 06853            if (is8bitSwFormatsVaapi)
 6854            {
 06855                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06856                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6857                {
 06858                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6859                }
 6860
 06861                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6862                {
 06863                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6864                }
 6865
 06866                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6867                {
 06868                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6869                }
 6870
 06871                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6872                {
 06873                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6874                }
 6875            }
 6876
 06877            if (is8_10bitSwFormatsVaapi)
 6878            {
 06879                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6880                {
 06881                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6882                }
 6883
 06884                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6885                {
 06886                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6887                }
 6888            }
 6889
 06890            if (is8_10_12bitSwFormatsVaapi)
 6891            {
 06892                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06893                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6894                {
 06895                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6896                }
 6897            }
 6898
 06899            return null;
 6900        }
 6901
 6902        public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream,
 6903        {
 06904            if (!OperatingSystem.IsMacOS()
 06905                || options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 6906            {
 06907                return null;
 6908            }
 6909
 06910            var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase
 06911                                    || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnore
 06912            var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, String
 06913            var is8_10_12bitSwFormatsVt = is8_10bitSwFormatsVt
 06914                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06915                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06916                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06917                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06918                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06919                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06920                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 06921            var isAv1SupportedSwFormatsVt = is8_10bitSwFormatsVt || string.Equals("yuv420p12le", videoStream.PixelFormat
 6922
 6923            // The related patches make videotoolbox hardware surface working is only available in jellyfin-ffmpeg 7.0.1
 06924            bool useHwSurface = (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface) && IsVideoToolboxFullSupp
 6925
 06926            if (is8bitSwFormatsVt)
 6927            {
 06928                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6929                {
 06930                    return GetHwaccelType(state, options, "vp8", bitDepth, useHwSurface);
 6931                }
 6932            }
 6933
 06934            if (is8_10bitSwFormatsVt)
 6935            {
 06936                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06937                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6938                {
 06939                    return GetHwaccelType(state, options, "h264", bitDepth, useHwSurface);
 6940                }
 6941
 06942                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6943                {
 06944                    return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
 6945                }
 6946            }
 6947
 06948            if (is8_10_12bitSwFormatsVt)
 6949            {
 06950                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06951                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6952                {
 06953                    return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
 6954                }
 6955
 06956                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06957                    && isAv1SupportedSwFormatsVt
 06958                    && _mediaEncoder.IsVideoToolboxAv1DecodeAvailable)
 6959                {
 06960                    return GetHwaccelType(state, options, "av1", bitDepth, useHwSurface);
 6961                }
 6962            }
 6963
 06964            return null;
 6965        }
 6966
 6967        public string GetRkmppVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6968        {
 06969            var isLinux = OperatingSystem.IsLinux();
 6970
 06971            if (!isLinux
 06972                || options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 6973            {
 06974                return null;
 6975            }
 6976
 06977            var inW = state.VideoStream?.Width;
 06978            var inH = state.VideoStream?.Height;
 06979            var reqW = state.BaseRequest.Width;
 06980            var reqH = state.BaseRequest.Height;
 06981            var reqMaxW = state.BaseRequest.MaxWidth;
 06982            var reqMaxH = state.BaseRequest.MaxHeight;
 6983
 6984            // rkrga RGA2e supports range from 1/16 to 16
 06985            if (!IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 16.0f))
 6986            {
 06987                return null;
 6988            }
 6989
 06990            var isRkmppOclSupported = IsRkmppFullSupported() && IsOpenclFullSupported();
 06991            var hwSurface = isRkmppOclSupported
 06992                && _mediaEncoder.SupportsFilter("alphasrc");
 6993
 6994            // rkrga RGA3 supports range from 1/8 to 8
 06995            var isAfbcSupported = hwSurface && IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 6996
 6997            // TODO: add more 8/10bit and 4:2:2 formats for Rkmpp after finishing the ffcheck tool
 06998            var is8bitSwFormatsRkmpp = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06999                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 07000            var is10bitSwFormatsRkmpp = string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIg
 07001            var is8_10bitSwFormatsRkmpp = is8bitSwFormatsRkmpp || is10bitSwFormatsRkmpp;
 7002
 7003            // nv15 and nv20 are bit-stream only formats
 07004            if (is10bitSwFormatsRkmpp && !hwSurface)
 7005            {
 07006                return null;
 7007            }
 7008
 07009            if (is8bitSwFormatsRkmpp)
 7010            {
 07011                if (string.Equals(videoStream.Codec, "mpeg1video", StringComparison.OrdinalIgnoreCase))
 7012                {
 07013                    return GetHwaccelType(state, options, "mpeg1video", bitDepth, hwSurface);
 7014                }
 7015
 07016                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 7017                {
 07018                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 7019                }
 7020
 07021                if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 7022                {
 07023                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
 7024                }
 7025
 07026                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 7027                {
 07028                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 7029                }
 7030            }
 7031
 07032            if (is8_10bitSwFormatsRkmpp)
 7033            {
 07034                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 07035                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 7036                {
 07037                    var accelType = GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 07038                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7039                }
 7040
 07041                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 07042                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 7043                {
 07044                    var accelType = GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 07045                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7046                }
 7047
 07048                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 7049                {
 07050                    var accelType = GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 07051                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7052                }
 7053
 07054                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 7055                {
 7056                    // there's an issue about AV1 AFBC on RK3588, disable it for now until it's fixed upstream
 07057                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 7058                }
 7059            }
 7060
 07061            return null;
 7062        }
 7063
 7064        /// <summary>
 7065        /// Gets the number of threads.
 7066        /// </summary>
 7067        /// <param name="state">Encoding state.</param>
 7068        /// <param name="encodingOptions">Encoding options.</param>
 7069        /// <param name="outputVideoCodec">Video codec to use.</param>
 7070        /// <returns>Number of threads.</returns>
 7071#nullable enable
 7072        public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVide
 7073        {
 07074            var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount;
 7075
 07076            if (threads <= 0)
 7077            {
 7078                // Automatically set thread count
 07079                return 0;
 7080            }
 7081
 07082            return Math.Min(threads, Environment.ProcessorCount);
 7083        }
 7084
 7085#nullable disable
 7086        public void TryStreamCopy(EncodingJobInfo state, EncodingOptions options)
 7087        {
 07088            if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream))
 7089            {
 07090                state.OutputVideoCodec = "copy";
 7091            }
 7092            else
 7093            {
 07094                var user = state.User;
 7095
 7096                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07097                if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
 7098                {
 07099                    state.OutputVideoCodec = "copy";
 7100                }
 7101            }
 7102
 07103            var preventHlsAudioCopy = state.TranscodingType is TranscodingJobType.Hls
 07104                && state.VideoStream is not null
 07105                && !IsCopyCodec(state.OutputVideoCodec)
 07106                && options.HlsAudioSeekStrategy is HlsAudioSeekStrategy.TranscodeAudio;
 7107
 07108            if (state.AudioStream is not null
 07109                && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs)
 07110                && !preventHlsAudioCopy)
 7111            {
 07112                state.OutputAudioCodec = "copy";
 7113            }
 7114            else
 7115            {
 07116                var user = state.User;
 7117
 7118                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07119                if (user is not null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
 7120                {
 07121                    state.OutputAudioCodec = "copy";
 7122                }
 7123            }
 07124        }
 7125
 7126        public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
 7127        {
 07128            var inputModifier = string.Empty;
 07129            var analyzeDurationArgument = string.Empty;
 7130
 7131            // Apply -analyzeduration as per the environment variable,
 7132            // otherwise ffmpeg will break on certain files due to default value is 0.
 07133            var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
 7134
 07135            if (state.MediaSource.AnalyzeDurationMs > 0)
 7136            {
 07137                analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToStr
 7138            }
 07139            else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
 7140            {
 07141                analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
 7142            }
 7143
 07144            if (!string.IsNullOrEmpty(analyzeDurationArgument))
 7145            {
 07146                inputModifier += " " + analyzeDurationArgument;
 7147            }
 7148
 07149            inputModifier = inputModifier.Trim();
 7150
 7151            // Apply -probesize if configured
 07152            var ffmpegProbeSize = _config.GetFFmpegProbeSize();
 7153
 07154            if (!string.IsNullOrEmpty(ffmpegProbeSize))
 7155            {
 07156                inputModifier += $" -probesize {ffmpegProbeSize}";
 7157            }
 7158
 07159            var userAgentParam = GetUserAgentParam(state);
 7160
 07161            if (!string.IsNullOrEmpty(userAgentParam))
 7162            {
 07163                inputModifier += " " + userAgentParam;
 7164            }
 7165
 07166            inputModifier = inputModifier.Trim();
 7167
 07168            var refererParam = GetRefererParam(state);
 7169
 07170            if (!string.IsNullOrEmpty(refererParam))
 7171            {
 07172                inputModifier += " " + refererParam;
 7173            }
 7174
 07175            inputModifier = inputModifier.Trim();
 7176
 07177            inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions, segmentContainer);
 07178            inputModifier = inputModifier.Trim();
 7179
 07180            if (state.InputProtocol == MediaProtocol.Rtsp)
 7181            {
 07182                inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
 7183            }
 7184
 07185            if (!string.IsNullOrEmpty(state.InputAudioSync))
 7186            {
 07187                inputModifier += " -async " + state.InputAudioSync;
 7188            }
 7189
 7190            // The -fps_mode option cannot be applied to input
 07191            if (!string.IsNullOrEmpty(state.InputVideoSync) && _mediaEncoder.EncoderVersion < new Version(5, 1))
 7192            {
 07193                inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion);
 7194            }
 7195
 07196            if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp)
 7197            {
 07198                inputModifier += " -re";
 7199            }
 07200            else if (encodingOptions.EnableSegmentDeletion
 07201                && state.VideoStream is not null
 07202                && state.TranscodingType == TranscodingJobType.Hls
 07203                && IsCopyCodec(state.OutputVideoCodec)
 07204                && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateOption)
 7205            {
 7206                // Set an input read rate limit 10x for using SegmentDeletion with stream-copy
 7207                // to prevent ffmpeg from exiting prematurely (due to fast drive)
 07208                inputModifier += " -readrate 10";
 7209            }
 7210
 07211            var flags = new List<string>();
 07212            if (state.IgnoreInputDts)
 7213            {
 07214                flags.Add("+igndts");
 7215            }
 7216
 07217            if (state.IgnoreInputIndex)
 7218            {
 07219                flags.Add("+ignidx");
 7220            }
 7221
 07222            if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
 7223            {
 07224                flags.Add("+genpts");
 7225            }
 7226
 07227            if (state.DiscardCorruptFramesInput)
 7228            {
 07229                flags.Add("+discardcorrupt");
 7230            }
 7231
 07232            if (state.EnableFastSeekInput)
 7233            {
 07234                flags.Add("+fastseek");
 7235            }
 7236
 07237            if (flags.Count > 0)
 7238            {
 07239                inputModifier += " -fflags " + string.Join(string.Empty, flags);
 7240            }
 7241
 07242            if (state.IsVideoRequest)
 7243            {
 07244                if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && encodingOpt
 7245                {
 07246                    var inputFormat = GetInputFormat(state.InputContainer);
 07247                    if (!string.IsNullOrEmpty(inputFormat))
 7248                    {
 07249                        inputModifier += " -f " + inputFormat;
 7250                    }
 7251                }
 7252            }
 7253
 07254            if (state.MediaSource.RequiresLooping)
 7255            {
 07256                inputModifier += " -stream_loop -1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2";
 7257            }
 7258
 07259            return inputModifier;
 7260        }
 7261
 7262        public void AttachMediaSourceInfo(
 7263            EncodingJobInfo state,
 7264            EncodingOptions encodingOptions,
 7265            MediaSourceInfo mediaSource,
 7266            string requestedUrl)
 7267        {
 07268            ArgumentNullException.ThrowIfNull(state);
 7269
 07270            ArgumentNullException.ThrowIfNull(mediaSource);
 7271
 07272            var path = mediaSource.Path;
 07273            var protocol = mediaSource.Protocol;
 7274
 07275            if (!string.IsNullOrEmpty(mediaSource.EncoderPath) && mediaSource.EncoderProtocol.HasValue)
 7276            {
 07277                path = mediaSource.EncoderPath;
 07278                protocol = mediaSource.EncoderProtocol.Value;
 7279            }
 7280
 07281            state.MediaPath = path;
 07282            state.InputProtocol = protocol;
 07283            state.InputContainer = mediaSource.Container;
 07284            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07285            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 7286
 07287            state.IsoType = mediaSource.IsoType;
 7288
 07289            if (mediaSource.Timestamp.HasValue)
 7290            {
 07291                state.InputTimestamp = mediaSource.Timestamp.Value;
 7292            }
 7293
 07294            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07295            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 07296            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
 7297
 07298            if ((state.ReadInputAtNativeFramerate && !state.IsSegmentedLiveStream)
 07299                || (mediaSource.Protocol == MediaProtocol.File
 07300                && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)))
 7301            {
 07302                state.InputVideoSync = "-1";
 07303                state.InputAudioSync = "1";
 7304            }
 7305
 07306            if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase)
 07307                || string.Equals(mediaSource.Container, "asf", StringComparison.OrdinalIgnoreCase))
 7308            {
 7309                // Seeing some stuttering when transcoding wma to audio-only HLS
 07310                state.InputAudioSync = "1";
 7311            }
 7312
 07313            var mediaStreams = mediaSource.MediaStreams;
 7314
 07315            if (state.IsVideoRequest)
 7316            {
 07317                var videoRequest = state.BaseRequest;
 7318
 07319                if (string.IsNullOrEmpty(videoRequest.VideoCodec))
 7320                {
 07321                    if (string.IsNullOrEmpty(requestedUrl))
 7322                    {
 07323                        requestedUrl = "test." + videoRequest.Container;
 7324                    }
 7325
 07326                    videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
 7327                }
 7328
 07329                state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
 07330                state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Su
 07331                state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
 07332                state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
 7333
 07334                if (state.SubtitleStream is not null && !state.SubtitleStream.IsExternal)
 7335                {
 07336                    state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !
 7337                }
 7338
 07339                EnforceResolutionLimit(state);
 7340
 07341                NormalizeSubtitleEmbed(state);
 7342            }
 7343            else
 7344            {
 07345                state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
 7346            }
 7347
 07348            state.MediaSource = mediaSource;
 7349
 07350            var request = state.BaseRequest;
 07351            var supportedAudioCodecs = state.SupportedAudioCodecs;
 07352            if (request is not null && supportedAudioCodecs is not null && supportedAudioCodecs.Length > 0)
 7353            {
 07354                var supportedAudioCodecsList = supportedAudioCodecs.ToList();
 7355
 07356                ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
 7357
 07358                state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray();
 7359
 07360                request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(_mediaEncoder.CanEncodeToAudioCodec)
 07361                    ?? state.SupportedAudioCodecs.FirstOrDefault();
 7362            }
 7363
 07364            var supportedVideoCodecs = state.SupportedVideoCodecs;
 07365            if (request is not null && supportedVideoCodecs is not null && supportedVideoCodecs.Length > 0)
 7366            {
 07367                var supportedVideoCodecsList = supportedVideoCodecs.ToList();
 7368
 07369                ShiftVideoCodecsIfNeeded(supportedVideoCodecsList, encodingOptions);
 7370
 07371                state.SupportedVideoCodecs = supportedVideoCodecsList.ToArray();
 7372
 07373                request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
 7374            }
 07375        }
 7376
 7377        private void ShiftAudioCodecsIfNeeded(List<string> audioCodecs, MediaStream audioStream)
 7378        {
 7379            // No need to shift if there is only one supported audio codec.
 07380            if (audioCodecs.Count < 2)
 7381            {
 07382                return;
 7383            }
 7384
 07385            var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
 07386            var shiftAudioCodecs = new List<string>();
 07387            if (inputChannels >= 6)
 7388            {
 7389                // DTS and TrueHD are not supported by HLS
 7390                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07391                shiftAudioCodecs.Add("dts");
 07392                shiftAudioCodecs.Add("truehd");
 7393            }
 7394            else
 7395            {
 7396                // Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
 7397                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07398                shiftAudioCodecs.Add("ac3");
 07399                shiftAudioCodecs.Add("eac3");
 7400            }
 7401
 07402            if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7403            {
 07404                return;
 7405            }
 7406
 07407            while (shiftAudioCodecs.Contains(audioCodecs[0], StringComparison.OrdinalIgnoreCase))
 7408            {
 07409                var removed = audioCodecs[0];
 07410                audioCodecs.RemoveAt(0);
 07411                audioCodecs.Add(removed);
 7412            }
 07413        }
 7414
 7415        private void ShiftVideoCodecsIfNeeded(List<string> videoCodecs, EncodingOptions encodingOptions)
 7416        {
 7417            // No need to shift if there is only one supported video codec.
 07418            if (videoCodecs.Count < 2)
 7419            {
 07420                return;
 7421            }
 7422
 7423            // Shift codecs to the end of list if it's not allowed.
 07424            var shiftVideoCodecs = new List<string>();
 07425            if (!encodingOptions.AllowHevcEncoding)
 7426            {
 07427                shiftVideoCodecs.Add("hevc");
 07428                shiftVideoCodecs.Add("h265");
 7429            }
 7430
 07431            if (!encodingOptions.AllowAv1Encoding)
 7432            {
 07433                shiftVideoCodecs.Add("av1");
 7434            }
 7435
 07436            if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7437            {
 07438                return;
 7439            }
 7440
 07441            while (shiftVideoCodecs.Contains(videoCodecs[0], StringComparison.OrdinalIgnoreCase))
 7442            {
 07443                var removed = videoCodecs[0];
 07444                videoCodecs.RemoveAt(0);
 07445                videoCodecs.Add(removed);
 7446            }
 07447        }
 7448
 7449        private void NormalizeSubtitleEmbed(EncodingJobInfo state)
 7450        {
 07451            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7452            {
 07453                return;
 7454            }
 7455
 7456            // This is tricky to remux in, after converting to dvdsub it's not positioned correctly
 7457            // Therefore, let's just burn it in
 07458            if (string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 7459            {
 07460                state.SubtitleDeliveryMethod = SubtitleDeliveryMethod.Encode;
 7461            }
 07462        }
 7463
 7464        public string GetSubtitleEmbedArguments(EncodingJobInfo state)
 7465        {
 07466            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7467            {
 07468                return string.Empty;
 7469            }
 7470
 07471            var format = state.SupportedSubtitleCodecs.FirstOrDefault();
 7472            string codec;
 7473
 07474            if (string.IsNullOrEmpty(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.Ordin
 7475            {
 07476                codec = "copy";
 7477            }
 7478            else
 7479            {
 07480                codec = format;
 7481            }
 7482
 07483            return " -codec:s:0 " + codec + " -disposition:s:0 default";
 7484        }
 7485
 7486        public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, Encoder
 7487        {
 7488            // Get the output codec name
 07489            var videoCodec = GetVideoEncoder(state, encodingOptions);
 7490
 07491            var format = string.Empty;
 07492            var keyFrame = string.Empty;
 07493            var outputPath = state.OutputFilePath;
 7494
 07495            if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase)
 07496                && state.BaseRequest.Context == EncodingContext.Streaming)
 7497            {
 7498                // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
 07499                format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov";
 7500            }
 7501
 07502            var threads = GetNumberOfThreads(state, encodingOptions, videoCodec);
 7503
 07504            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7505
 07506            return string.Format(
 07507                CultureInfo.InvariantCulture,
 07508                "{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
 07509                inputModifier,
 07510                GetInputArgument(state, encodingOptions, null),
 07511                keyFrame,
 07512                GetMapArgs(state),
 07513                GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset),
 07514                threads,
 07515                GetProgressiveVideoAudioArguments(state, encodingOptions),
 07516                GetSubtitleEmbedArguments(state),
 07517                format,
 07518                outputPath).Trim();
 7519        }
 7520
 7521        public string GetOutputFFlags(EncodingJobInfo state)
 7522        {
 07523            var flags = new List<string>();
 07524            if (state.GenPtsOutput)
 7525            {
 07526                flags.Add("+genpts");
 7527            }
 7528
 07529            if (flags.Count > 0)
 7530            {
 07531                return " -fflags " + string.Join(string.Empty, flags);
 7532            }
 7533
 07534            return string.Empty;
 7535        }
 7536
 7537        public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoC
 7538        {
 07539            var args = "-codec:v:0 " + videoCodec;
 7540
 07541            if (state.BaseRequest.EnableMpegtsM2TsMode)
 7542            {
 07543                args += " -mpegts_m2ts_mode 1";
 7544            }
 7545
 07546            if (IsCopyCodec(videoCodec))
 7547            {
 07548                if (state.VideoStream is not null
 07549                    && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
 07550                    && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
 7551                {
 07552                    string bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Video);
 07553                    if (!string.IsNullOrEmpty(bitStreamArgs))
 7554                    {
 07555                        args += " " + bitStreamArgs;
 7556                    }
 7557                }
 7558
 07559                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7560                {
 07561                    args += " -copyts -avoid_negative_ts disabled -start_at_zero";
 7562                }
 7563
 07564                if (!state.RunTimeTicks.HasValue)
 7565                {
 07566                    args += " -fflags +genpts";
 7567                }
 7568            }
 7569            else
 7570            {
 07571                var keyFrameArg = string.Format(
 07572                    CultureInfo.InvariantCulture,
 07573                    " -force_key_frames \"expr:gte(t,n_forced*{0})\"",
 07574                    5);
 7575
 07576                args += keyFrameArg;
 7577
 07578                var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream &&
 7579
 07580                var hasCopyTs = false;
 7581
 7582                // video processing filters.
 07583                var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
 7584
 07585                var negativeMapArgs = GetNegativeMapArgsByFilters(state, videoProcessParam);
 7586
 07587                args = negativeMapArgs + args + videoProcessParam;
 7588
 07589                hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
 7590
 07591                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7592                {
 07593                    if (!hasCopyTs)
 7594                    {
 07595                        args += " -copyts";
 7596                    }
 7597
 07598                    args += " -avoid_negative_ts disabled";
 7599
 07600                    if (!(state.SubtitleStream is not null && state.SubtitleStream.IsExternal && !state.SubtitleStream.I
 7601                    {
 07602                        args += " -start_at_zero";
 7603                    }
 7604                }
 7605
 07606                var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset);
 7607
 07608                if (!string.IsNullOrEmpty(qualityParam))
 7609                {
 07610                    args += " " + qualityParam.Trim();
 7611                }
 7612            }
 7613
 07614            if (!string.IsNullOrEmpty(state.OutputVideoSync))
 7615            {
 07616                args += GetVideoSyncOption(state.OutputVideoSync, _mediaEncoder.EncoderVersion);
 7617            }
 7618
 07619            args += GetOutputFFlags(state);
 7620
 07621            return args;
 7622        }
 7623
 7624        public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions)
 7625        {
 7626            // If the video doesn't have an audio stream, return a default.
 07627            if (state.AudioStream is null && state.VideoStream is not null)
 7628            {
 07629                return string.Empty;
 7630            }
 7631
 7632            // Get the output codec name
 07633            var codec = GetAudioEncoder(state);
 7634
 07635            var args = "-codec:a:0 " + codec;
 7636
 07637            if (IsCopyCodec(codec))
 7638            {
 07639                return args;
 7640            }
 7641
 07642            var channels = state.OutputAudioChannels;
 7643
 07644            var useDownMixAlgorithm = state.AudioStream is not null
 07645                                      && DownMixAlgorithmsHelper.AlgorithmFilterStrings.ContainsKey((encodingOptions.Dow
 7646
 07647            if (channels.HasValue && !useDownMixAlgorithm)
 7648            {
 07649                args += " -ac " + channels.Value;
 7650            }
 7651
 07652            var bitrate = state.OutputAudioBitrate;
 07653            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
 7654            {
 07655                var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2);
 07656                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7657                {
 07658                    args += vbrParam;
 7659                }
 7660                else
 7661                {
 07662                    args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
 7663                }
 7664            }
 7665
 07666            if (state.OutputAudioSampleRate.HasValue)
 7667            {
 07668                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
 7669            }
 7670
 07671            args += GetAudioFilterParam(state, encodingOptions);
 7672
 07673            return args;
 7674        }
 7675
 7676        public string GetProgressiveAudioFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string 
 7677        {
 07678            var audioTranscodeParams = new List<string>();
 7679
 07680            var bitrate = state.OutputAudioBitrate;
 07681            var channels = state.OutputAudioChannels;
 07682            var outputCodec = state.OutputAudioCodec;
 7683
 07684            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
 7685            {
 07686                var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2);
 07687                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7688                {
 07689                    audioTranscodeParams.Add(vbrParam);
 7690                }
 7691                else
 7692                {
 07693                    audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
 7694                }
 7695            }
 7696
 07697            if (channels.HasValue)
 7698            {
 07699                audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)
 7700            }
 7701
 07702            if (!string.IsNullOrEmpty(outputCodec))
 7703            {
 07704                audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
 7705            }
 7706
 07707            if (GetAudioEncoder(state).StartsWith("pcm_", StringComparison.Ordinal))
 7708            {
 07709                audioTranscodeParams.Add(string.Concat("-f ", GetAudioEncoder(state).AsSpan(4)));
 07710                audioTranscodeParams.Add("-ar " + state.BaseRequest.AudioBitRate);
 7711            }
 7712
 07713            if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
 7714            {
 7715                // opus only supports specific sampling rates
 07716                var sampleRate = state.OutputAudioSampleRate;
 07717                if (sampleRate.HasValue)
 7718                {
 07719                    var sampleRateValue = sampleRate.Value switch
 07720                    {
 07721                        <= 8000 => 8000,
 07722                        <= 12000 => 12000,
 07723                        <= 16000 => 16000,
 07724                        <= 24000 => 24000,
 07725                        _ => 48000
 07726                    };
 7727
 07728                    audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
 7729                }
 7730            }
 7731
 7732            // Copy the movflags from GetProgressiveVideoFullCommandLine
 7733            // See #9248 and the associated PR for why this is needed
 07734            if (_mp4ContainerNames.Contains(state.OutputContainer))
 7735            {
 07736                audioTranscodeParams.Add("-movflags empty_moov+delay_moov");
 7737            }
 7738
 07739            var threads = GetNumberOfThreads(state, encodingOptions, null);
 7740
 07741            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7742
 07743            return string.Format(
 07744                CultureInfo.InvariantCulture,
 07745                "{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"",
 07746                inputModifier,
 07747                GetInputArgument(state, encodingOptions, null),
 07748                threads,
 07749                " -vn",
 07750                string.Join(' ', audioTranscodeParams),
 07751                outputPath,
 07752                string.Empty,
 07753                string.Empty,
 07754                string.Empty).Trim();
 7755        }
 7756
 7757        public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
 7758        {
 07759            var index = 0;
 07760            var length = mediaStreams.Count;
 7761
 07762            for (var i = 0; i < length; i++)
 7763            {
 07764                var currentMediaStream = mediaStreams[i];
 07765                if (currentMediaStream == streamToFind)
 7766                {
 07767                    return index;
 7768                }
 7769
 07770                if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
 7771                {
 07772                    index++;
 7773                }
 7774            }
 7775
 07776            return -1;
 7777        }
 7778
 7779        public static bool IsCopyCodec(string codec)
 7780        {
 07781            return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
 7782        }
 7783
 7784        private static bool ShouldEncodeSubtitle(EncodingJobInfo state)
 7785        {
 07786            return state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
 07787                   || (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec));
 7788        }
 7789
 7790        public static string GetVideoSyncOption(string videoSync, Version encoderVersion)
 7791        {
 07792            if (string.IsNullOrEmpty(videoSync))
 7793            {
 07794                return string.Empty;
 7795            }
 7796
 07797            if (encoderVersion >= new Version(5, 1))
 7798            {
 07799                if (int.TryParse(videoSync, CultureInfo.InvariantCulture, out var vsync))
 7800                {
 07801                    return vsync switch
 07802                    {
 07803                        -1 => " -fps_mode auto",
 07804                        0 => " -fps_mode passthrough",
 07805                        1 => " -fps_mode cfr",
 07806                        2 => " -fps_mode vfr",
 07807                        _ => string.Empty
 07808                    };
 7809                }
 7810
 07811                return string.Empty;
 7812            }
 7813
 7814            // -vsync is deprecated in FFmpeg 5.1 and will be removed in the future.
 07815            return $" -vsync {videoSync}";
 7816        }
 7817    }
 7818}

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)
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)