< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.MediaEncoding.EncodingHelper
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
Line coverage
4%
Covered lines: 183
Uncovered lines: 3596
Coverable lines: 3779
Total lines: 7967
Line coverage: 4.8%
Branch coverage
2%
Covered branches: 112
Total branches: 3741
Branch coverage: 2.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 3/6/2026 - 12:14:09 AM Line coverage: 0.6% (26/3734) Branch coverage: 0% (0/3729) Total lines: 78183/14/2026 - 12:13:58 AM Line coverage: 0.6% (26/3746) Branch coverage: 0% (0/3735) Total lines: 78513/24/2026 - 12:13:40 AM Line coverage: 0.7% (27/3752) Branch coverage: 0% (0/3739) Total lines: 78623/25/2026 - 12:13:35 AM Line coverage: 0.7% (27/3755) Branch coverage: 0% (0/3739) Total lines: 78693/30/2026 - 12:14:34 AM Line coverage: 0.7% (27/3755) Branch coverage: 0% (0/3739) Total lines: 78773/31/2026 - 12:14:24 AM Line coverage: 0.7% (27/3755) Branch coverage: 0% (0/3743) Total lines: 78754/7/2026 - 12:14:03 AM Line coverage: 0.7% (27/3754) Branch coverage: 0% (0/3743) Total lines: 78894/30/2026 - 12:14:58 AM Line coverage: 0.7% (27/3758) Branch coverage: 0% (0/3751) Total lines: 78935/4/2026 - 12:15:16 AM Line coverage: 0.7% (27/3765) Branch coverage: 0% (0/3757) Total lines: 79055/11/2026 - 12:15:59 AM Line coverage: 0.7% (27/3771) Branch coverage: 0% (0/3765) Total lines: 79165/13/2026 - 12:15:27 AM Line coverage: 0.7% (27/3772) Branch coverage: 0% (0/3765) Total lines: 79175/20/2026 - 12:15:44 AM Line coverage: 3.9% (149/3780) Branch coverage: 2.2% (84/3767) Total lines: 79385/22/2026 - 12:15:17 AM Line coverage: 3.9% (149/3780) Branch coverage: 2.2% (84/3767) Total lines: 79425/27/2026 - 12:15:38 AM Line coverage: 3.9% (149/3780) Branch coverage: 2.2% (84/3767) Total lines: 79465/29/2026 - 12:15:07 AM Line coverage: 4.8% (183/3795) Branch coverage: 2.9% (112/3773) Total lines: 79716/11/2026 - 12:16:04 AM Line coverage: 4.8% (183/3779) Branch coverage: 2.9% (112/3741) Total lines: 7967 3/6/2026 - 12:14:09 AM Line coverage: 0.6% (26/3734) Branch coverage: 0% (0/3729) Total lines: 78183/14/2026 - 12:13:58 AM Line coverage: 0.6% (26/3746) Branch coverage: 0% (0/3735) Total lines: 78513/24/2026 - 12:13:40 AM Line coverage: 0.7% (27/3752) Branch coverage: 0% (0/3739) Total lines: 78623/25/2026 - 12:13:35 AM Line coverage: 0.7% (27/3755) Branch coverage: 0% (0/3739) Total lines: 78693/30/2026 - 12:14:34 AM Line coverage: 0.7% (27/3755) Branch coverage: 0% (0/3739) Total lines: 78773/31/2026 - 12:14:24 AM Line coverage: 0.7% (27/3755) Branch coverage: 0% (0/3743) Total lines: 78754/7/2026 - 12:14:03 AM Line coverage: 0.7% (27/3754) Branch coverage: 0% (0/3743) Total lines: 78894/30/2026 - 12:14:58 AM Line coverage: 0.7% (27/3758) Branch coverage: 0% (0/3751) Total lines: 78935/4/2026 - 12:15:16 AM Line coverage: 0.7% (27/3765) Branch coverage: 0% (0/3757) Total lines: 79055/11/2026 - 12:15:59 AM Line coverage: 0.7% (27/3771) Branch coverage: 0% (0/3765) Total lines: 79165/13/2026 - 12:15:27 AM Line coverage: 0.7% (27/3772) Branch coverage: 0% (0/3765) Total lines: 79175/20/2026 - 12:15:44 AM Line coverage: 3.9% (149/3780) Branch coverage: 2.2% (84/3767) Total lines: 79385/22/2026 - 12:15:17 AM Line coverage: 3.9% (149/3780) Branch coverage: 2.2% (84/3767) Total lines: 79425/27/2026 - 12:15:38 AM Line coverage: 3.9% (149/3780) Branch coverage: 2.2% (84/3767) Total lines: 79465/29/2026 - 12:15:07 AM Line coverage: 4.8% (183/3795) Branch coverage: 2.9% (112/3773) Total lines: 79716/11/2026 - 12:16:04 AM Line coverage: 4.8% (183/3779) Branch coverage: 2.9% (112/3741) Total lines: 7967

Coverage delta

Coverage delta 4 -4

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%342180%
IsVideoToolboxTonemapAvailable(...)0%210140%
IsDeinterlaceAvailable(...)0%2040%
IsVideoStreamHevcRext(...)0%342180%
GetVideoEncoder(...)14.28%1091421.42%
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(...)70%322068.75%
GetInputVideoHwaccelArgs(...)2.5%130971203.41%
GetInputArgument(...)52.77%1063662.22%
IsH264(...)0%2040%
IsH265(...)0%2040%
IsAv1(...)0%620%
IsAAC(...)50%22100%
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(...)92.85%1414100%
GetCopiedAudioTrimBsf(...)92.85%1414100%
GetSegmentFileExtension(...)50%2266.66%
GetVideoBitrateParam(...)0%1806420%
GetEncoderParam(...)0%8010890%
NormalizeTranscodingLevel(...)0%506220%
GetTextSubtitlesFilter(...)0%272160%
GetFramerateParam(...)0%156120%
GetHlsVideoKeyFrameArguments(...)0%1806420%
GetVideoQualityParam(...)0%325801800%
CanStreamCopyVideo(...)0%197401400%
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(...)10%231050%
GetMapArgs(...)50%843666.66%
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%2162460%
GetNvidiaVidFilterChain(...)0%110100%
GetNvidiaVidFiltersPrefered(...)0%7140840%
GetAmdVidFilterChain(...)0%210140%
GetAmdDx11VidFiltersPrefered(...)0%7140840%
GetIntelVidFilterChain(...)0%506220%
GetIntelQsvDx11VidFiltersPrefered(...)0%197401400%
GetIntelQsvVaapiVidFiltersPrefered(...)0%165121280%
GetVaapiVidFilterChain(...)0%1190340%
GetIntelVaapiFullVidFiltersPrefered(...)0%117721080%
GetAmdVaapiFullVidFiltersPrefered(...)0%9312960%
GetVaapiLimitedVidFiltersPrefered(...)0%7482860%
GetAppleVidFilterChain(...)0%156120%
GetAppleVidFiltersPreferred(...)0%4970700%
GetRkmppVidFilterChain(...)0%272160%
GetRkmppVidFiltersPrefered(...)0%150061220%
GetVideoProcessingFilterParam(...)0%3192560%
GetOverwriteColorPropertiesParam(...)0%2040%
GetInputHdrParam(...)0%620%
GetOutputSdrParam(...)0%2040%
GetVideoColorBitDepth(...)0%600240%
GetHardwareVideoDecoder(...)17.74%16846225%
GetHwDecoderName(...)0%1190340%
GetHwaccelType(...)0%150061220%
GetQsvHwVidDecoder(...)0%3192560%
GetNvdecVidDecoder(...)0%2162460%
GetAmfVidDecoder(...)0%1056320%
GetVaapiVidDecoder(...)0%2756520%
GetVideotoolboxVidDecoder(...)0%2352480%
GetRkmppVidDecoder(...)0%2756520%
GetNumberOfThreads(...)0%4260%
TryStreamCopy(...)0%600240%
GetFfmpegAnalyzeDurationArg(...)50%7671.42%
GetFfmpegProbesizeArg()50%2275%
GetInputModifier(...)0%3660600%
AttachMediaSourceInfo(...)0%1806420%
ShiftAudioCodecsIfNeeded(...)0%110100%
ShiftVideoCodecsIfNeeded(...)0%110100%
NormalizeSubtitleEmbed(...)0%4260%
GetSubtitleEmbedArguments(...)0%7280%
GetProgressiveVideoFullCommandLine(...)0%2040%
GetOutputFFlags(...)0%2040%
GetProgressiveVideoArguments(...)0%1482380%
GetProgressiveVideoAudioArguments(...)0%600240%
GetProgressiveAudioFullCommandLine(...)0%930300%
FindIndex(...)66.66%6688.88%
IsCopyCodec(...)100%11100%
ShouldEncodeSubtitle(...)75%44100%
NeedsExternalSubtitleMuxing(...)62.5%88100%
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 string.
 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 ContainerValidationRegexStr = @"^[a-zA-Z0-9\-\._,|]{0,40}$";
 42
 43        /// <summary>
 44        /// The level validation regex string.
 45        /// This regular expression matches strings representing a double.
 46        /// </summary>
 47        public const string LevelValidationRegexStr = @"-?[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)
 4468        private readonly Version _minKerneli915Hang = new Version(5, 18);
 4469        private readonly Version _maxKerneli915Hang = new Version(6, 1, 3);
 4470        private readonly Version _minFixedKernel60i915Hang = new Version(6, 0, 18);
 4471        private readonly Version _minKernelVersionAmdVkFmtModifier = new Version(5, 15);
 72
 4473        private readonly Version _minFFmpegImplicitHwaccel = new Version(6, 0);
 4474        private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0);
 4475        private readonly Version _minFFmpegOclCuTonemapMode = new Version(5, 1, 3);
 4476        private readonly Version _minFFmpegSvtAv1Params = new Version(5, 1);
 4477        private readonly Version _minFFmpegVaapiH26xEncA53CcSei = new Version(6, 0);
 4478        private readonly Version _minFFmpegReadrateOption = new Version(5, 0);
 4479        private readonly Version _minFFmpegWorkingVtHwSurface = new Version(7, 0, 1);
 4480        private readonly Version _minFFmpegDisplayRotationOption = new Version(6, 0);
 4481        private readonly Version _minFFmpegAdvancedTonemapMode = new Version(7, 0, 1);
 4482        private readonly Version _minFFmpegAlteredVaVkInterop = new Version(7, 0, 1);
 4483        private readonly Version _minFFmpegQsvVppTonemapOption = new Version(7, 0, 1);
 4484        private readonly Version _minFFmpegQsvVppOutRangeOption = new Version(7, 0, 1);
 4485        private readonly Version _minFFmpegVaapiDeviceVendorId = new Version(7, 0, 1);
 4486        private readonly Version _minFFmpegQsvVppScaleModeOption = new Version(6, 0);
 4487        private readonly Version _minFFmpegRkmppHevcDecDoviRpu = new Version(7, 1, 1);
 4488        private readonly Version _minFFmpegReadrateCatchupOption = new Version(8, 0);
 4489        private readonly Version _minFFmpegNoiseBsfDrop = new Version(5, 0);
 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        {
 44168            _appPaths = appPaths;
 44169            _mediaEncoder = mediaEncoder;
 44170            _subtitleEncoder = subtitleEncoder;
 44171            _config = config;
 44172            _configurationManager = configurationManager;
 44173            _pathManager = pathManager;
 44174        }
 175
 176        private enum DynamicHdrMetadataRemovalPlan
 177        {
 178            None,
 179            RemoveDovi,
 180            RemoveHdr10Plus,
 181        }
 182
 183        /// <summary>
 184        /// The codec validation regex.
 185        /// This regular expression matches strings that consist of alphanumeric characters, hyphens,
 186        /// periods, underscores, commas, and vertical bars, with a length between 0 and 40 characters.
 187        /// This should matches all common valid codecs.
 188        /// </summary>
 189        [GeneratedRegex(ContainerValidationRegexStr)]
 190        public static partial Regex ContainerValidationRegex();
 191
 192        /// <summary>
 193        /// The level validation regex string.
 194        /// This regular expression matches strings representing a double.
 195        /// </summary>
 196        [GeneratedRegex(LevelValidationRegexStr)]
 197        public static partial Regex LevelValidationRegex();
 198
 199        [GeneratedRegex(@"\s+")]
 200        private static partial Regex WhiteSpaceRegex();
 201
 202        public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0203            => GetH26xOrAv1Encoder("libx264", "h264", state, encodingOptions);
 204
 205        public string GetH265Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0206            => GetH26xOrAv1Encoder("libx265", "hevc", state, encodingOptions);
 207
 208        public string GetAv1Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0209            => GetH26xOrAv1Encoder("libsvtav1", "av1", state, encodingOptions);
 210
 211        private string GetH26xOrAv1Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptio
 212        {
 213            // Only use alternative encoders for video files.
 214            // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying
 215            // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such 
 0216            if (state.VideoType == VideoType.VideoFile)
 217            {
 0218                var hwType = encodingOptions.HardwareAccelerationType;
 219
 0220                var codecMap = new Dictionary<HardwareAccelerationType, string>()
 0221                {
 0222                    { HardwareAccelerationType.amf,                  hwEncoder + "_amf" },
 0223                    { HardwareAccelerationType.nvenc,                hwEncoder + "_nvenc" },
 0224                    { HardwareAccelerationType.qsv,                  hwEncoder + "_qsv" },
 0225                    { HardwareAccelerationType.vaapi,                hwEncoder + "_vaapi" },
 0226                    { HardwareAccelerationType.videotoolbox,         hwEncoder + "_videotoolbox" },
 0227                    { HardwareAccelerationType.v4l2m2m,              hwEncoder + "_v4l2m2m" },
 0228                    { HardwareAccelerationType.rkmpp,                hwEncoder + "_rkmpp" },
 0229                };
 230
 0231                if (hwType != HardwareAccelerationType.none
 0232                    && encodingOptions.EnableHardwareEncoding
 0233                    && codecMap.TryGetValue(hwType, out var preferredEncoder)
 0234                    && _mediaEncoder.SupportsEncoder(preferredEncoder))
 235                {
 0236                    return preferredEncoder;
 237                }
 238            }
 239
 0240            return defaultEncoder;
 241        }
 242
 243        private string GetMjpegEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 244        {
 0245            if (state.VideoType == VideoType.VideoFile)
 246            {
 0247                var hwType = encodingOptions.HardwareAccelerationType;
 248
 249                // Only enable VA-API MJPEG encoder on Intel iHD driver.
 250                // Legacy platforms supported ONLY by i965 do not support MJPEG encoder.
 0251                if (hwType == HardwareAccelerationType.vaapi
 0252                    && !_mediaEncoder.IsVaapiDeviceInteliHD)
 253                {
 0254                    return _defaultMjpegEncoder;
 255                }
 256
 0257                if (hwType != HardwareAccelerationType.none
 0258                    && encodingOptions.EnableHardwareEncoding
 0259                    && _mjpegCodecMap.TryGetValue(hwType, out var preferredEncoder)
 0260                    && _mediaEncoder.SupportsEncoder(preferredEncoder))
 261                {
 0262                    return preferredEncoder;
 263                }
 264            }
 265
 0266            return _defaultMjpegEncoder;
 267        }
 268
 269        private bool IsVaapiSupported(EncodingJobInfo state)
 270        {
 271            // vaapi will throw an error with this input
 272            // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99.
 0273            if (string.Equals(state.VideoStream?.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 274            {
 0275                return false;
 276            }
 277
 0278            return _mediaEncoder.SupportsHwaccel("vaapi");
 279        }
 280
 281        private bool IsVaapiFullSupported()
 282        {
 0283            return _mediaEncoder.SupportsHwaccel("drm")
 0284                   && _mediaEncoder.SupportsHwaccel("vaapi")
 0285                   && _mediaEncoder.SupportsFilter("scale_vaapi")
 0286                   && _mediaEncoder.SupportsFilter("deinterlace_vaapi")
 0287                   && _mediaEncoder.SupportsFilter("tonemap_vaapi")
 0288                   && _mediaEncoder.SupportsFilter("procamp_vaapi")
 0289                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVaapiFrameSync)
 0290                   && _mediaEncoder.SupportsFilter("transpose_vaapi")
 0291                   && _mediaEncoder.SupportsFilter("hwupload_vaapi");
 292        }
 293
 294        private bool IsRkmppFullSupported()
 295        {
 0296            return _mediaEncoder.SupportsHwaccel("rkmpp")
 0297                   && _mediaEncoder.SupportsFilter("scale_rkrga")
 0298                   && _mediaEncoder.SupportsFilter("vpp_rkrga")
 0299                   && _mediaEncoder.SupportsFilter("overlay_rkrga");
 300        }
 301
 302        private bool IsOpenclFullSupported()
 303        {
 0304            return _mediaEncoder.SupportsHwaccel("opencl")
 0305                   && _mediaEncoder.SupportsFilter("scale_opencl")
 0306                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390)
 0307                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclFrameSync);
 308
 309            // Let transpose_opencl optional for the time being.
 310        }
 311
 312        private bool IsCudaFullSupported()
 313        {
 0314            return _mediaEncoder.SupportsHwaccel("cuda")
 0315                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat)
 0316                   && _mediaEncoder.SupportsFilter("yadif_cuda")
 0317                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName)
 0318                   && _mediaEncoder.SupportsFilter("overlay_cuda")
 0319                   && _mediaEncoder.SupportsFilter("hwupload_cuda");
 320
 321            // Let transpose_cuda optional for the time being.
 322        }
 323
 324        private bool IsVulkanFullSupported()
 325        {
 0326            return _mediaEncoder.SupportsHwaccel("vulkan")
 0327                   && _mediaEncoder.SupportsFilter("libplacebo")
 0328                   && _mediaEncoder.SupportsFilter("scale_vulkan")
 0329                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVulkanFrameSync)
 0330                   && _mediaEncoder.SupportsFilter("transpose_vulkan")
 0331                   && _mediaEncoder.SupportsFilter("flip_vulkan");
 332        }
 333
 334        private bool IsVideoToolboxFullSupported()
 335        {
 0336            return _mediaEncoder.SupportsHwaccel("videotoolbox")
 0337                && _mediaEncoder.SupportsFilter("yadif_videotoolbox")
 0338                && _mediaEncoder.SupportsFilter("overlay_videotoolbox")
 0339                && _mediaEncoder.SupportsFilter("tonemap_videotoolbox")
 0340                && _mediaEncoder.SupportsFilter("scale_vt");
 341
 342            // Let transpose_vt optional for the time being.
 343        }
 344
 345        private bool IsSwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 346        {
 0347            if (state.VideoStream is null
 0348                || GetVideoColorBitDepth(state) < 10
 0349                || !_mediaEncoder.SupportsFilter("tonemapx"))
 350            {
 0351                return false;
 352            }
 353
 0354            return state.VideoStream.VideoRange == VideoRange.HDR;
 355        }
 356
 357        private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 358        {
 0359            if (state.VideoStream is null
 0360                || !options.EnableTonemapping
 0361                || GetVideoColorBitDepth(state) < 10)
 362            {
 0363                return false;
 364            }
 365
 0366            if (state.VideoStream.VideoRange == VideoRange.HDR
 0367                && state.VideoStream.VideoRangeType == VideoRangeType.DOVI)
 368            {
 369                // Only native SW decoder, HW accelerator and hevc_rkmpp decoder can parse dovi rpu.
 0370                var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 371
 0372                var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 0373                if (isRkmppDecoder
 0374                    && _mediaEncoder.EncoderVersion >= _minFFmpegRkmppHevcDecDoviRpu
 0375                    && string.Equals(state.VideoStream?.Codec, "hevc", StringComparison.OrdinalIgnoreCase))
 376                {
 0377                    return true;
 378                }
 379
 0380                var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 0381                var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 0382                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 0383                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 0384                var isVideoToolBoxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 0385                return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder || isVideoToolBoxDecoder;
 386            }
 387
 388            // GPU tonemapping supports all HDR RangeTypes
 0389            return state.VideoStream.VideoRange == VideoRange.HDR;
 390        }
 391
 392        private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 393        {
 0394            if (state.VideoStream is null)
 395            {
 0396                return false;
 397            }
 398
 399            // libplacebo has partial Dolby Vision to SDR tonemapping support.
 0400            return options.EnableTonemapping
 0401                   && state.VideoStream.VideoRange == VideoRange.HDR
 0402                   && GetVideoColorBitDepth(state) == 10;
 403        }
 404
 405        private bool IsIntelVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 406        {
 0407            if (state.VideoStream is null
 0408                || !options.EnableVppTonemapping
 0409                || GetVideoColorBitDepth(state) < 10)
 410            {
 0411                return false;
 412            }
 413
 414            // prefer 'tonemap_vaapi' over 'vpp_qsv' on Linux for supporting Gen9/KBLx.
 415            // 'vpp_qsv' requires VPL, which is only supported on Gen12/TGLx and newer.
 0416            if (OperatingSystem.IsWindows()
 0417                && options.HardwareAccelerationType == HardwareAccelerationType.qsv
 0418                && _mediaEncoder.EncoderVersion < _minFFmpegQsvVppTonemapOption)
 419            {
 0420                return false;
 421            }
 422
 0423            return state.VideoStream.VideoRange == VideoRange.HDR
 0424                   && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
 0425                       || IsHdr10Plus(state.VideoStream)
 0426                       || IsDoviWithHdr10Bl(state.VideoStream));
 427        }
 428
 429        private bool IsVideoToolboxTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 430        {
 0431            if (state.VideoStream is null
 0432                || !options.EnableVideoToolboxTonemapping
 0433                || GetVideoColorBitDepth(state) < 10)
 434            {
 0435                return false;
 436            }
 437
 438            // Certain DV profile 5 video works in Safari with direct playing, but the VideoToolBox does not produce cor
 439            // All other HDR formats working.
 0440            return state.VideoStream.VideoRange == VideoRange.HDR
 0441                   && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
 0442                       || IsHdr10Plus(state.VideoStream)
 0443                       || IsDoviWithHdr10Bl(state.VideoStream)
 0444                       || state.VideoStream.VideoRangeType == VideoRangeType.HLG);
 445        }
 446
 447        private static bool IsDeinterlaceAvailable(EncodingJobInfo state)
 448        {
 0449            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 0450            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 0451            return doDeintH264 || doDeintHevc;
 452        }
 453
 454        private bool IsVideoStreamHevcRext(EncodingJobInfo state)
 455        {
 0456            var videoStream = state.VideoStream;
 0457            if (videoStream is null)
 458            {
 0459                return false;
 460            }
 461
 0462            return string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 0463                   && (string.Equals(videoStream.Profile, "Rext", StringComparison.OrdinalIgnoreCase)
 0464                       || string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 0465                       || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 0466                       || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 0467                       || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 0468                       || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase)
 0469                       || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)
 0470                       || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase));
 471        }
 472
 473        /// <summary>
 474        /// Gets the name of the output video codec.
 475        /// </summary>
 476        /// <param name="state">Encoding state.</param>
 477        /// <param name="encodingOptions">Encoding options.</param>
 478        /// <returns>Encoder string.</returns>
 479        public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 480        {
 4481            var codec = state.OutputVideoCodec;
 482
 4483            if (!string.IsNullOrEmpty(codec))
 484            {
 0485                if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 486                {
 0487                    return GetAv1Encoder(state, encodingOptions);
 488                }
 489
 0490                if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 0491                    || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
 492                {
 0493                    return GetH265Encoder(state, encodingOptions);
 494                }
 495
 0496                if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
 497                {
 0498                    return GetH264Encoder(state, encodingOptions);
 499                }
 500
 0501                if (string.Equals(codec, "mjpeg", StringComparison.OrdinalIgnoreCase))
 502                {
 0503                    return GetMjpegEncoder(state, encodingOptions);
 504                }
 505
 0506                if (ContainerValidationRegex().IsMatch(codec))
 507                {
 0508                    return codec.ToLowerInvariant();
 509                }
 510            }
 511
 4512            return "copy";
 513        }
 514
 515        /// <summary>
 516        /// Gets the user agent param.
 517        /// </summary>
 518        /// <param name="state">The state.</param>
 519        /// <returns>System.String.</returns>
 520        public string GetUserAgentParam(EncodingJobInfo state)
 521        {
 0522            if (state.RemoteHttpHeaders.TryGetValue("User-Agent", out string useragent))
 523            {
 0524                return "-user_agent \"" + useragent + "\"";
 525            }
 526
 0527            return string.Empty;
 528        }
 529
 530        /// <summary>
 531        /// Gets the referer param.
 532        /// </summary>
 533        /// <param name="state">The state.</param>
 534        /// <returns>System.String.</returns>
 535        public string GetRefererParam(EncodingJobInfo state)
 536        {
 0537            if (state.RemoteHttpHeaders.TryGetValue("Referer", out string referer))
 538            {
 0539                return "-referer \"" + referer + "\"";
 540            }
 541
 0542            return string.Empty;
 543        }
 544
 545        public static string GetInputFormat(string container)
 546        {
 0547            if (string.IsNullOrEmpty(container) || !ContainerValidationRegex().IsMatch(container))
 548            {
 0549                return null;
 550            }
 551
 0552            container = container.Replace("mkv", "matroska", StringComparison.OrdinalIgnoreCase);
 553
 0554            if (string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
 555            {
 0556                return "mpegts";
 557            }
 558
 559            // For these need to find out the ffmpeg names
 0560            if (string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase))
 561            {
 0562                return null;
 563            }
 564
 0565            if (string.Equals(container, "wmv", StringComparison.OrdinalIgnoreCase))
 566            {
 0567                return null;
 568            }
 569
 0570            if (string.Equals(container, "mts", StringComparison.OrdinalIgnoreCase))
 571            {
 0572                return null;
 573            }
 574
 0575            if (string.Equals(container, "vob", StringComparison.OrdinalIgnoreCase))
 576            {
 0577                return null;
 578            }
 579
 0580            if (string.Equals(container, "mpg", StringComparison.OrdinalIgnoreCase))
 581            {
 0582                return null;
 583            }
 584
 0585            if (string.Equals(container, "mpeg", StringComparison.OrdinalIgnoreCase))
 586            {
 0587                return null;
 588            }
 589
 0590            if (string.Equals(container, "rec", StringComparison.OrdinalIgnoreCase))
 591            {
 0592                return null;
 593            }
 594
 0595            if (string.Equals(container, "dvr-ms", StringComparison.OrdinalIgnoreCase))
 596            {
 0597                return null;
 598            }
 599
 0600            if (string.Equals(container, "ogm", StringComparison.OrdinalIgnoreCase))
 601            {
 0602                return null;
 603            }
 604
 0605            if (string.Equals(container, "divx", StringComparison.OrdinalIgnoreCase))
 606            {
 0607                return null;
 608            }
 609
 0610            if (string.Equals(container, "tp", StringComparison.OrdinalIgnoreCase))
 611            {
 0612                return null;
 613            }
 614
 0615            if (string.Equals(container, "rmvb", StringComparison.OrdinalIgnoreCase))
 616            {
 0617                return null;
 618            }
 619
 0620            if (string.Equals(container, "rtp", StringComparison.OrdinalIgnoreCase))
 621            {
 0622                return null;
 623            }
 624
 625            // Seeing reported failures here, not sure yet if this is related to specifying input format
 0626            if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase))
 627            {
 0628                return null;
 629            }
 630
 631            // obviously don't do this for strm files
 0632            if (string.Equals(container, "strm", StringComparison.OrdinalIgnoreCase))
 633            {
 0634                return null;
 635            }
 636
 637            // ISO files don't have an ffmpeg format
 0638            if (string.Equals(container, "iso", StringComparison.OrdinalIgnoreCase))
 639            {
 0640                return null;
 641            }
 642
 0643            return container;
 644        }
 645
 646        /// <summary>
 647        /// Gets decoder from a codec.
 648        /// </summary>
 649        /// <param name="codec">Codec to use.</param>
 650        /// <returns>Decoder string.</returns>
 651        public string GetDecoderFromCodec(string codec)
 652        {
 653            // For these need to find out the ffmpeg names
 0654            if (string.Equals(codec, "mp2", StringComparison.OrdinalIgnoreCase))
 655            {
 0656                return null;
 657            }
 658
 0659            if (string.Equals(codec, "aac_latm", StringComparison.OrdinalIgnoreCase))
 660            {
 0661                return null;
 662            }
 663
 0664            if (string.Equals(codec, "eac3", StringComparison.OrdinalIgnoreCase))
 665            {
 0666                return null;
 667            }
 668
 0669            if (_mediaEncoder.SupportsDecoder(codec))
 670            {
 0671                return codec;
 672            }
 673
 0674            return null;
 675        }
 676
 677        /// <summary>
 678        /// Infers the audio codec based on the url.
 679        /// </summary>
 680        /// <param name="container">Container to use.</param>
 681        /// <returns>Codec string.</returns>
 682        public string InferAudioCodec(string container)
 683        {
 0684            if (string.IsNullOrWhiteSpace(container))
 685            {
 686                // this may not work, but if the client is that broken we cannot do anything better
 0687                return "aac";
 688            }
 689
 0690            var inferredCodec = container.ToLowerInvariant();
 691
 0692            return inferredCodec switch
 0693            {
 0694                "ogg" or "oga" or "ogv" or "webm" or "webma" => "opus",
 0695                "m4a" or "m4b" or "mp4" or "mov" or "mkv" or "mka" => "aac",
 0696                "ts" or "avi" or "flv" or "f4v" or "swf" => "mp3",
 0697                _ => inferredCodec
 0698            };
 699        }
 700
 701        /// <summary>
 702        /// Infers the video codec.
 703        /// </summary>
 704        /// <param name="url">The URL.</param>
 705        /// <returns>System.Nullable{VideoCodecs}.</returns>
 706        public string InferVideoCodec(string url)
 707        {
 0708            var ext = Path.GetExtension(url.AsSpan());
 709
 0710            if (ext.Equals(".asf", StringComparison.OrdinalIgnoreCase))
 711            {
 0712                return "wmv";
 713            }
 714
 0715            if (ext.Equals(".webm", StringComparison.OrdinalIgnoreCase))
 716            {
 717                // TODO: this may not always mean VP8, as the codec ages
 0718                return "vp8";
 719            }
 720
 0721            if (ext.Equals(".ogg", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ogv", StringComparison.OrdinalIgn
 722            {
 0723                return "theora";
 724            }
 725
 0726            if (ext.Equals(".m3u8", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ts", StringComparison.OrdinalIgn
 727            {
 0728                return "h264";
 729            }
 730
 0731            return "copy";
 732        }
 733
 734        public int GetVideoProfileScore(string videoCodec, string videoProfile)
 735        {
 736            // strip spaces because they may be stripped out on the query string
 0737            string profile = videoProfile.Replace(" ", string.Empty, StringComparison.Ordinal);
 0738            if (string.Equals("h264", videoCodec, StringComparison.OrdinalIgnoreCase))
 739            {
 0740                return Array.FindIndex(_videoProfilesH264, x => string.Equals(x, profile, StringComparison.OrdinalIgnore
 741            }
 742
 0743            if (string.Equals("hevc", videoCodec, StringComparison.OrdinalIgnoreCase))
 744            {
 0745                return Array.FindIndex(_videoProfilesH265, x => string.Equals(x, profile, StringComparison.OrdinalIgnore
 746            }
 747
 0748            if (string.Equals("av1", videoCodec, StringComparison.OrdinalIgnoreCase))
 749            {
 0750                return Array.FindIndex(_videoProfilesAv1, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreC
 751            }
 752
 0753            return -1;
 754        }
 755
 756        /// <summary>
 757        /// Gets the audio encoder.
 758        /// </summary>
 759        /// <param name="state">The state.</param>
 760        /// <returns>System.String.</returns>
 761        public string GetAudioEncoder(EncodingJobInfo state)
 762        {
 0763            var codec = state.OutputAudioCodec;
 764
 0765            if (!ContainerValidationRegex().IsMatch(codec))
 766            {
 0767                codec = "aac";
 768            }
 769
 0770            if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
 771            {
 772                // Use Apple's aac encoder if available as it provides best audio quality
 0773                if (_mediaEncoder.SupportsEncoder("aac_at"))
 774                {
 0775                    return "aac_at";
 776                }
 777
 778                // Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
 0779                if (_mediaEncoder.SupportsEncoder("libfdk_aac"))
 780                {
 0781                    return "libfdk_aac";
 782                }
 783
 0784                return "aac";
 785            }
 786
 0787            if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
 788            {
 0789                return "libmp3lame";
 790            }
 791
 0792            if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase))
 793            {
 0794                return "libvorbis";
 795            }
 796
 0797            if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
 798            {
 0799                return "libopus";
 800            }
 801
 0802            if (string.Equals(codec, "flac", StringComparison.OrdinalIgnoreCase))
 803            {
 0804                return "flac";
 805            }
 806
 0807            if (string.Equals(codec, "dts", StringComparison.OrdinalIgnoreCase))
 808            {
 0809                return "dca";
 810            }
 811
 0812            if (string.Equals(codec, "alac", StringComparison.OrdinalIgnoreCase))
 813            {
 814                // The ffmpeg upstream breaks the AudioToolbox ALAC encoder in version 6.1 but fixes it in version 7.0.
 815                // Since ALAC is lossless in quality and the AudioToolbox encoder is not faster,
 816                // its only benefit is a smaller file size.
 817                // To prevent problems, use the ffmpeg native encoder instead.
 0818                return "alac";
 819            }
 820
 0821            return codec.ToLowerInvariant();
 822        }
 823
 824        private string GetRkmppDeviceArgs(string alias)
 825        {
 0826            alias ??= RkmppAlias;
 827
 828            // device selection in rk is not supported.
 0829            return " -init_hw_device rkmpp=" + alias;
 830        }
 831
 832        private string GetVideoToolboxDeviceArgs(string alias)
 833        {
 0834            alias ??= VideotoolboxAlias;
 835
 836            // device selection in vt is not supported.
 0837            return " -init_hw_device videotoolbox=" + alias;
 838        }
 839
 840        private string GetCudaDeviceArgs(int deviceIndex, string alias)
 841        {
 0842            alias ??= CudaAlias;
 0843            deviceIndex = deviceIndex >= 0
 0844                ? deviceIndex
 0845                : 0;
 846
 0847            return string.Format(
 0848                CultureInfo.InvariantCulture,
 0849                " -init_hw_device cuda={0}:{1}",
 0850                alias,
 0851                deviceIndex);
 852        }
 853
 854        private string GetVulkanDeviceArgs(int deviceIndex, string deviceName, string srcDeviceAlias, string alias)
 855        {
 0856            alias ??= VulkanAlias;
 0857            deviceIndex = deviceIndex >= 0
 0858                ? deviceIndex
 0859                : 0;
 0860            var vendorOpts = string.IsNullOrEmpty(deviceName)
 0861                ? ":" + deviceIndex
 0862                : ":" + "\"" + deviceName + "\"";
 0863            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0864                ? vendorOpts
 0865                : "@" + srcDeviceAlias;
 866
 0867            return string.Format(
 0868                CultureInfo.InvariantCulture,
 0869                " -init_hw_device vulkan={0}{1}",
 0870                alias,
 0871                options);
 872        }
 873
 874        private string GetOpenclDeviceArgs(int deviceIndex, string deviceVendorName, string srcDeviceAlias, string alias
 875        {
 0876            alias ??= OpenclAlias;
 0877            deviceIndex = deviceIndex >= 0
 0878                ? deviceIndex
 0879                : 0;
 0880            var vendorOpts = string.IsNullOrEmpty(deviceVendorName)
 0881                ? ":0.0"
 0882                : ":." + deviceIndex + ",device_vendor=\"" + deviceVendorName + "\"";
 0883            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0884                ? vendorOpts
 0885                : "@" + srcDeviceAlias;
 886
 0887            return string.Format(
 0888                CultureInfo.InvariantCulture,
 0889                " -init_hw_device opencl={0}{1}",
 0890                alias,
 0891                options);
 892        }
 893
 894        private string GetD3d11vaDeviceArgs(int deviceIndex, string deviceVendorId, string alias)
 895        {
 0896            alias ??= D3d11vaAlias;
 0897            deviceIndex = deviceIndex >= 0 ? deviceIndex : 0;
 0898            var options = string.IsNullOrEmpty(deviceVendorId)
 0899                ? deviceIndex.ToString(CultureInfo.InvariantCulture)
 0900                : ",vendor=" + deviceVendorId;
 901
 0902            return string.Format(
 0903                CultureInfo.InvariantCulture,
 0904                " -init_hw_device d3d11va={0}:{1}",
 0905                alias,
 0906                options);
 907        }
 908
 909        private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string vendorId, st
 910        {
 0911            alias ??= VaapiAlias;
 0912            var haveVendorId = !string.IsNullOrEmpty(vendorId)
 0913                && _mediaEncoder.EncoderVersion >= _minFFmpegVaapiDeviceVendorId;
 914
 915            // Priority: 'renderNodePath' > 'vendorId' > 'kernelDriver'
 0916            var driverOpts = File.Exists(renderNodePath)
 0917                ? renderNodePath
 0918                : (haveVendorId ? $",vendor_id={vendorId}" : (string.IsNullOrEmpty(kernelDriver) ? string.Empty : $",ker
 919
 920            // 'driver' behaves similarly to env LIBVA_DRIVER_NAME
 0921            driverOpts += string.IsNullOrEmpty(driver) ? string.Empty : ",driver=" + driver;
 922
 0923            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0924                ? (string.IsNullOrEmpty(driverOpts) ? string.Empty : ":" + driverOpts)
 0925                : "@" + srcDeviceAlias;
 926
 0927            return string.Format(
 0928                CultureInfo.InvariantCulture,
 0929                " -init_hw_device vaapi={0}{1}",
 0930                alias,
 0931                options);
 932        }
 933
 934        private string GetDrmDeviceArgs(string renderNodePath, string alias)
 935        {
 0936            alias ??= DrmAlias;
 0937            renderNodePath = renderNodePath ?? "/dev/dri/renderD128";
 938
 0939            return string.Format(
 0940                CultureInfo.InvariantCulture,
 0941                " -init_hw_device drm={0}:{1}",
 0942                alias,
 0943                renderNodePath);
 944        }
 945
 946        private string GetQsvDeviceArgs(string renderNodePath, string alias)
 947        {
 0948            var arg = " -init_hw_device qsv=" + (alias ?? QsvAlias);
 0949            if (OperatingSystem.IsLinux())
 950            {
 951                // derive qsv from vaapi device
 0952                return GetVaapiDeviceArgs(renderNodePath, "iHD", "i915", "0x8086", null, VaapiAlias) + arg + "@" + Vaapi
 953            }
 954
 0955            if (OperatingSystem.IsWindows())
 956            {
 957                // on Windows, the deviceIndex is an int
 0958                if (int.TryParse(renderNodePath, NumberStyles.Integer, CultureInfo.InvariantCulture, out int deviceIndex
 959                {
 0960                    return GetD3d11vaDeviceArgs(deviceIndex, string.Empty, D3d11vaAlias) + arg + "@" + D3d11vaAlias;
 961                }
 962
 963                // derive qsv from d3d11va device
 0964                return GetD3d11vaDeviceArgs(0, "0x8086", D3d11vaAlias) + arg + "@" + D3d11vaAlias;
 965            }
 966
 0967            return null;
 968        }
 969
 970        private string GetFilterHwDeviceArgs(string alias)
 971        {
 0972            return string.IsNullOrEmpty(alias)
 0973                ? string.Empty
 0974                : " -filter_hw_device " + alias;
 975        }
 976
 977        public string GetGraphicalSubCanvasSize(EncodingJobInfo state)
 978        {
 979            // DVBSUB uses the fixed canvas size 720x576
 4980            if (state.SubtitleStream is not null
 4981                && ShouldEncodeSubtitle(state)
 4982                && !state.SubtitleStream.IsTextSubtitleStream
 4983                && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 984            {
 2985                var subtitleWidth = state.SubtitleStream?.Width;
 2986                var subtitleHeight = state.SubtitleStream?.Height;
 987
 2988                if (subtitleWidth.HasValue
 2989                    && subtitleHeight.HasValue
 2990                    && subtitleWidth.Value > 0
 2991                    && subtitleHeight.Value > 0)
 992                {
 0993                    return string.Format(
 0994                        CultureInfo.InvariantCulture,
 0995                        " -canvas_size {0}x{1}",
 0996                        subtitleWidth.Value,
 0997                        subtitleHeight.Value);
 998                }
 999            }
 1000
 41001            return string.Empty;
 1002        }
 1003
 1004        /// <summary>
 1005        /// Gets the input video hwaccel argument.
 1006        /// </summary>
 1007        /// <param name="state">Encoding state.</param>
 1008        /// <param name="options">Encoding options.</param>
 1009        /// <returns>Input video hwaccel arguments.</returns>
 1010        public string GetInputVideoHwaccelArgs(EncodingJobInfo state, EncodingOptions options)
 1011        {
 41012            if (!state.IsVideoRequest)
 1013            {
 01014                return string.Empty;
 1015            }
 1016
 41017            var vidEncoder = GetVideoEncoder(state, options) ?? string.Empty;
 41018            if (IsCopyCodec(vidEncoder))
 1019            {
 41020                return string.Empty;
 1021            }
 1022
 01023            var args = new StringBuilder();
 01024            var isWindows = OperatingSystem.IsWindows();
 01025            var isLinux = OperatingSystem.IsLinux();
 01026            var isMacOS = OperatingSystem.IsMacOS();
 01027            var optHwaccelType = options.HardwareAccelerationType;
 01028            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 01029            var isHwTonemapAvailable = IsHwTonemapAvailable(state, options);
 1030
 01031            if (optHwaccelType == HardwareAccelerationType.vaapi)
 1032            {
 01033                if (!isLinux || !_mediaEncoder.SupportsHwaccel("vaapi"))
 1034                {
 01035                    return string.Empty;
 1036                }
 1037
 01038                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01039                var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01040                if (!isVaapiDecoder && !isVaapiEncoder)
 1041                {
 01042                    return string.Empty;
 1043                }
 1044
 01045                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 1046                {
 01047                    args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "iHD", null, null, null, VaapiAlias));
 1048                }
 01049                else if (_mediaEncoder.IsVaapiDeviceInteli965)
 1050                {
 1051                    // Only override i965 since it has lower priority than iHD in libva lookup.
 01052                    Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME", "i965");
 01053                    Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME_JELLYFIN", "i965");
 01054                    args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "i965", null, null, null, VaapiAlias));
 1055                }
 1056
 01057                var filterDevArgs = string.Empty;
 01058                var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
 1059
 01060                if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 1061                {
 01062                    if (doOclTonemap && !isVaapiDecoder)
 1063                    {
 01064                        args.Append(GetOpenclDeviceArgs(0, null, VaapiAlias, OpenclAlias));
 01065                        filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1066                    }
 1067                }
 01068                else if (_mediaEncoder.IsVaapiDeviceAmd)
 1069                {
 1070                    // Disable AMD EFC feature since it's still unstable in upstream Mesa.
 01071                    Environment.SetEnvironmentVariable("AMD_DEBUG", "noefc");
 1072
 01073                    if (IsVulkanFullSupported()
 01074                        && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 01075                        && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 1076                    {
 01077                        args.Append(GetDrmDeviceArgs(options.VaapiDevice, DrmAlias));
 01078                        args.Append(GetVaapiDeviceArgs(null, null, null, null, DrmAlias, VaapiAlias));
 01079                        args.Append(GetVulkanDeviceArgs(0, null, DrmAlias, VulkanAlias));
 1080
 1081                        // libplacebo wants an explicitly set vulkan filter device.
 01082                        filterDevArgs = GetFilterHwDeviceArgs(VulkanAlias);
 1083                    }
 1084                    else
 1085                    {
 01086                        args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, null, null, VaapiAlias));
 01087                        filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias);
 1088
 01089                        if (doOclTonemap)
 1090                        {
 1091                            // ROCm/ROCr OpenCL runtime
 01092                            args.Append(GetOpenclDeviceArgs(0, "Advanced Micro Devices", null, OpenclAlias));
 01093                            filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1094                        }
 1095                    }
 1096                }
 01097                else if (doOclTonemap)
 1098                {
 01099                    args.Append(GetOpenclDeviceArgs(0, null, null, OpenclAlias));
 01100                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1101                }
 1102
 01103                args.Append(filterDevArgs);
 1104            }
 01105            else if (optHwaccelType == HardwareAccelerationType.qsv)
 1106            {
 01107                if ((!isLinux && !isWindows) || !_mediaEncoder.SupportsHwaccel("qsv"))
 1108                {
 01109                    return string.Empty;
 1110                }
 1111
 01112                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 01113                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01114                var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 01115                var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 01116                var isHwDecoder = isQsvDecoder || isVaapiDecoder || isD3d11vaDecoder;
 01117                if (!isHwDecoder && !isQsvEncoder)
 1118                {
 01119                    return string.Empty;
 1120                }
 1121
 01122                args.Append(GetQsvDeviceArgs(options.QsvDevice, QsvAlias));
 01123                var filterDevArgs = GetFilterHwDeviceArgs(QsvAlias);
 1124                // child device used by qsv.
 01125                if (_mediaEncoder.SupportsHwaccel("vaapi") || _mediaEncoder.SupportsHwaccel("d3d11va"))
 1126                {
 01127                    if (isHwTonemapAvailable && IsOpenclFullSupported())
 1128                    {
 01129                        var srcAlias = isLinux ? VaapiAlias : D3d11vaAlias;
 01130                        args.Append(GetOpenclDeviceArgs(0, null, srcAlias, OpenclAlias));
 01131                        if (!isHwDecoder)
 1132                        {
 01133                            filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1134                        }
 1135                    }
 1136                }
 1137
 01138                args.Append(filterDevArgs);
 1139            }
 01140            else if (optHwaccelType == HardwareAccelerationType.nvenc)
 1141            {
 01142                if ((!isLinux && !isWindows) || !IsCudaFullSupported())
 1143                {
 01144                    return string.Empty;
 1145                }
 1146
 01147                var isCuvidDecoder = vidDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase);
 01148                var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 01149                var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 01150                var isHwDecoder = isNvdecDecoder || isCuvidDecoder;
 01151                if (!isHwDecoder && !isNvencEncoder)
 1152                {
 01153                    return string.Empty;
 1154                }
 1155
 01156                args.Append(GetCudaDeviceArgs(0, CudaAlias))
 01157                     .Append(GetFilterHwDeviceArgs(CudaAlias));
 1158            }
 01159            else if (optHwaccelType == HardwareAccelerationType.amf)
 1160            {
 01161                if (!isWindows || !_mediaEncoder.SupportsHwaccel("d3d11va"))
 1162                {
 01163                    return string.Empty;
 1164                }
 1165
 01166                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 01167                var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 01168                if (!isD3d11vaDecoder && !isAmfEncoder)
 1169                {
 01170                    return string.Empty;
 1171                }
 1172
 1173                // no dxva video processor hw filter.
 01174                args.Append(GetD3d11vaDeviceArgs(0, "0x1002", D3d11vaAlias));
 01175                var filterDevArgs = string.Empty;
 01176                if (IsOpenclFullSupported())
 1177                {
 01178                    args.Append(GetOpenclDeviceArgs(0, null, D3d11vaAlias, OpenclAlias));
 01179                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1180                }
 1181
 01182                args.Append(filterDevArgs);
 1183            }
 01184            else if (optHwaccelType == HardwareAccelerationType.videotoolbox)
 1185            {
 01186                if (!isMacOS || !_mediaEncoder.SupportsHwaccel("videotoolbox"))
 1187                {
 01188                    return string.Empty;
 1189                }
 1190
 01191                var isVideotoolboxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 01192                var isVideotoolboxEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 01193                if (!isVideotoolboxDecoder && !isVideotoolboxEncoder)
 1194                {
 01195                    return string.Empty;
 1196                }
 1197
 1198                // videotoolbox hw filter does not require device selection
 01199                args.Append(GetVideoToolboxDeviceArgs(VideotoolboxAlias));
 1200            }
 01201            else if (optHwaccelType == HardwareAccelerationType.rkmpp)
 1202            {
 01203                if (!isLinux || !_mediaEncoder.SupportsHwaccel("rkmpp"))
 1204                {
 01205                    return string.Empty;
 1206                }
 1207
 01208                var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 01209                var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 01210                if (!isRkmppDecoder && !isRkmppEncoder)
 1211                {
 01212                    return string.Empty;
 1213                }
 1214
 01215                args.Append(GetRkmppDeviceArgs(RkmppAlias));
 1216
 01217                var filterDevArgs = string.Empty;
 01218                var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
 1219
 01220                if (doOclTonemap && !isRkmppDecoder)
 1221                {
 01222                    args.Append(GetOpenclDeviceArgs(0, null, RkmppAlias, OpenclAlias));
 01223                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1224                }
 1225
 01226                args.Append(filterDevArgs);
 1227            }
 1228
 01229            if (!string.IsNullOrEmpty(vidDecoder))
 1230            {
 01231                args.Append(vidDecoder);
 1232            }
 1233
 01234            return args.ToString().Trim();
 1235        }
 1236
 1237        /// <summary>
 1238        /// Gets the input argument.
 1239        /// </summary>
 1240        /// <param name="state">Encoding state.</param>
 1241        /// <param name="options">Encoding options.</param>
 1242        /// <param name="segmentContainer">Segment Container.</param>
 1243        /// <returns>Input arguments.</returns>
 1244        public string GetInputArgument(EncodingJobInfo state, EncodingOptions options, string segmentContainer)
 1245        {
 41246            var arg = new StringBuilder();
 41247            var inputVidHwaccelArgs = GetInputVideoHwaccelArgs(state, options);
 1248
 41249            if (!string.IsNullOrEmpty(inputVidHwaccelArgs))
 1250            {
 01251                arg.Append(inputVidHwaccelArgs);
 1252            }
 1253
 41254            var canvasArgs = GetGraphicalSubCanvasSize(state);
 41255            if (!string.IsNullOrEmpty(canvasArgs))
 1256            {
 01257                arg.Append(canvasArgs);
 1258            }
 1259
 41260            if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
 1261            {
 01262                var concatFilePath = Path.Join(_configurationManager.CommonApplicationPaths.CachePath, "concat", state.M
 01263                if (!File.Exists(concatFilePath))
 1264                {
 01265                    _mediaEncoder.GenerateConcatConfig(state.MediaSource, concatFilePath);
 1266                }
 1267
 01268                arg.Append(" -f concat -safe 0 -i \"")
 01269                    .Append(concatFilePath)
 01270                    .Append("\" ");
 1271            }
 1272            else
 1273            {
 41274                arg.Append(" -i ")
 41275                    .Append(_mediaEncoder.GetInputPathArgument(state));
 1276            }
 1277
 41278            if (NeedsExternalSubtitleMuxing(state))
 1279            {
 41280                var subtitlePath = state.SubtitleStream.Path;
 41281                var isGraphicalBurnIn = ShouldEncodeSubtitle(state) && !state.SubtitleStream.IsTextSubtitleStream;
 1282
 1283                // dvdsub/vobsub graphical subtitles use .sub+.idx pairs
 41284                var subtitleExtension = Path.GetExtension(subtitlePath.AsSpan());
 41285                if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase))
 1286                {
 41287                    var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
 41288                    if (File.Exists(idxFile))
 1289                    {
 21290                        subtitlePath = idxFile;
 1291                    }
 1292                }
 1293
 1294                // Use analyzeduration also for subtitle streams to improve resolution detection  with streams inside MK
 41295                var analyzeDurationArgument = GetFfmpegAnalyzeDurationArg(state);
 41296                if (!string.IsNullOrEmpty(analyzeDurationArgument))
 1297                {
 01298                    arg.Append(' ').Append(analyzeDurationArgument);
 1299                }
 1300
 1301                // Apply probesize, too, if configured
 41302                var ffmpegProbeSizeArgument = GetFfmpegProbesizeArg();
 41303                if (!string.IsNullOrEmpty(ffmpegProbeSizeArgument))
 1304                {
 01305                    arg.Append(' ').Append(ffmpegProbeSizeArgument);
 1306                }
 1307
 1308                // Also seek the external subtitles stream.
 41309                var seekSubParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 41310                if (!string.IsNullOrEmpty(seekSubParam))
 1311                {
 01312                    arg.Append(' ').Append(seekSubParam);
 1313                }
 1314
 41315                if (isGraphicalBurnIn && !string.IsNullOrEmpty(canvasArgs))
 1316                {
 01317                    arg.Append(canvasArgs);
 1318                }
 1319
 41320                arg.Append(" -i file:\"").Append(subtitlePath).Append('\"');
 1321            }
 1322
 41323            if (state.AudioStream is not null && state.AudioStream.IsExternal)
 1324            {
 1325                // Also seek the external audio stream.
 01326                var seekAudioParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01327                if (!string.IsNullOrEmpty(seekAudioParam))
 1328                {
 01329                    arg.Append(' ').Append(seekAudioParam);
 1330                }
 1331
 01332                arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
 1333            }
 1334
 1335            // Disable auto inserted SW scaler for HW decoders in case of changed resolution.
 41336            var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
 41337            if (!isSwDecoder)
 1338            {
 01339                arg.Append(" -noautoscale");
 1340            }
 1341
 41342            return arg.ToString();
 1343        }
 1344
 1345        /// <summary>
 1346        /// Determines whether the specified stream is H264.
 1347        /// </summary>
 1348        /// <param name="stream">The stream.</param>
 1349        /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
 1350        public static bool IsH264(MediaStream stream)
 1351        {
 01352            var codec = stream.Codec ?? string.Empty;
 1353
 01354            return codec.Contains("264", StringComparison.OrdinalIgnoreCase)
 01355                    || codec.Contains("avc", StringComparison.OrdinalIgnoreCase);
 1356        }
 1357
 1358        public static bool IsH265(MediaStream stream)
 1359        {
 01360            var codec = stream.Codec ?? string.Empty;
 1361
 01362            return codec.Contains("265", StringComparison.OrdinalIgnoreCase)
 01363                || codec.Contains("hevc", StringComparison.OrdinalIgnoreCase);
 1364        }
 1365
 1366        public static bool IsAv1(MediaStream stream)
 1367        {
 01368            var codec = stream.Codec ?? string.Empty;
 1369
 01370            return codec.Contains("av1", StringComparison.OrdinalIgnoreCase);
 1371        }
 1372
 1373        public static bool IsAAC(MediaStream stream)
 1374        {
 101375            var codec = stream.Codec ?? string.Empty;
 1376
 101377            return codec.Contains("aac", StringComparison.OrdinalIgnoreCase);
 1378        }
 1379
 1380        public static bool IsDoviWithHdr10Bl(MediaStream stream)
 1381        {
 01382            var rangeType = stream?.VideoRangeType;
 1383
 01384            return rangeType is VideoRangeType.DOVIWithHDR10
 01385                or VideoRangeType.DOVIWithEL
 01386                or VideoRangeType.DOVIWithHDR10Plus
 01387                or VideoRangeType.DOVIWithELHDR10Plus
 01388                or VideoRangeType.DOVIInvalid;
 1389        }
 1390
 1391        public static bool IsDovi(MediaStream stream)
 1392        {
 01393            var rangeType = stream?.VideoRangeType;
 1394
 01395            return IsDoviWithHdr10Bl(stream)
 01396                   || (rangeType is VideoRangeType.DOVI
 01397                       or VideoRangeType.DOVIWithHLG
 01398                       or VideoRangeType.DOVIWithSDR);
 1399        }
 1400
 1401        public static bool IsHdr10Plus(MediaStream stream)
 1402        {
 01403            var rangeType = stream?.VideoRangeType;
 1404
 01405            return rangeType is VideoRangeType.HDR10Plus
 01406                       or VideoRangeType.DOVIWithHDR10Plus
 01407                       or VideoRangeType.DOVIWithELHDR10Plus;
 1408        }
 1409
 1410        /// <summary>
 1411        /// Check if dynamic HDR metadata should be removed during stream copy.
 1412        /// Please note this check assumes the range check has already been done
 1413        /// and trivial fallbacks like HDR10+ to HDR10, DOVIWithHDR10 to HDR10 is already checked.
 1414        /// </summary>
 1415        private static DynamicHdrMetadataRemovalPlan ShouldRemoveDynamicHdrMetadata(EncodingJobInfo state)
 1416        {
 01417            var videoStream = state.VideoStream;
 01418            if (videoStream.VideoRange is not VideoRange.HDR)
 1419            {
 01420                return DynamicHdrMetadataRemovalPlan.None;
 1421            }
 1422
 01423            var requestedRangeTypes = state.GetRequestedRangeTypes(state.VideoStream.Codec);
 01424            if (requestedRangeTypes.Length == 0)
 1425            {
 01426                return DynamicHdrMetadataRemovalPlan.None;
 1427            }
 1428
 01429            var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ordinal
 01430            var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIg
 01431            var requestHasDOVIwithEL = requestedRangeTypes.Contains(VideoRangeType.DOVIWithEL.ToString(), StringComparis
 01432            var requestHasDOVIwithELHDR10plus = requestedRangeTypes.Contains(VideoRangeType.DOVIWithELHDR10Plus.ToString
 1433
 01434            var shouldRemoveHdr10Plus = false;
 1435            // Case 1: Client supports HDR10, does not support DOVI with EL but EL presets
 01436            var shouldRemoveDovi = (!requestHasDOVIwithEL && requestHasHDR10) && videoStream.VideoRangeType == VideoRang
 1437
 1438            // Case 2: Client supports DOVI, does not support broken DOVI config
 1439            // Client does not report DOVI support should be allowed to copy bad data for remuxing as HDR10 players woul
 01440            shouldRemoveDovi = shouldRemoveDovi || (requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVII
 1441
 1442            // Special case: we have a video with both EL and HDR10+
 1443            // If the client supports EL but not in the case of coexistence with HDR10+, remove HDR10+ for compatibility
 1444            // Otherwise, remove DOVI if the client is not a DOVI player
 01445            if (videoStream.VideoRangeType == VideoRangeType.DOVIWithELHDR10Plus)
 1446            {
 01447                shouldRemoveHdr10Plus = requestHasDOVIwithEL && !requestHasDOVIwithELHDR10plus;
 01448                shouldRemoveDovi = shouldRemoveDovi || !shouldRemoveHdr10Plus;
 1449            }
 1450
 01451            if (shouldRemoveDovi)
 1452            {
 01453                return DynamicHdrMetadataRemovalPlan.RemoveDovi;
 1454            }
 1455
 1456            // If the client is a Dolby Vision Player, remove the HDR10+ metadata to avoid playback issues
 01457            shouldRemoveHdr10Plus = shouldRemoveHdr10Plus || (requestHasDOVI && videoStream.VideoRangeType == VideoRange
 01458            return shouldRemoveHdr10Plus ? DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus : DynamicHdrMetadataRemovalPlan
 1459        }
 1460
 1461        private bool CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan plan, MediaStream videoStream)
 1462        {
 01463            return plan switch
 01464            {
 01465                DynamicHdrMetadataRemovalPlan.RemoveDovi => _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFil
 01466                                                            || (IsH265(videoStream) && _mediaEncoder.SupportsBitStreamFi
 01467                                                            || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStreamFil
 01468                DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus => (IsH265(videoStream) && _mediaEncoder.SupportsBitStream
 01469                                                                 || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStre
 01470                _ => true,
 01471            };
 1472        }
 1473
 1474        public bool IsDoviRemoved(EncodingJobInfo state)
 1475        {
 01476            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01477                                              && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Remove
 1478        }
 1479
 1480        public bool IsHdr10PlusRemoved(EncodingJobInfo state)
 1481        {
 01482            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01483                                                  && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Re
 1484        }
 1485
 1486        public string GetBitStreamArgs(EncodingJobInfo state, MediaStreamType streamType)
 1487        {
 01488            if (state is null)
 1489            {
 01490                return null;
 1491            }
 1492
 01493            var stream = streamType switch
 01494            {
 01495                MediaStreamType.Audio => state.AudioStream,
 01496                MediaStreamType.Video => state.VideoStream,
 01497                _ => state.VideoStream
 01498            };
 1499            // TODO This is auto inserted into the mpegts mux so it might not be needed.
 1500            // https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
 01501            if (IsH264(stream))
 1502            {
 01503                return "-bsf:v h264_mp4toannexb";
 1504            }
 1505
 01506            if (IsAAC(stream))
 1507            {
 1508                // Convert adts header(mpegts) to asc header(mp4).
 01509                return "-bsf:a aac_adtstoasc";
 1510            }
 1511
 01512            if (IsH265(stream))
 1513            {
 01514                var filter = "-bsf:v hevc_mp4toannexb";
 1515
 1516                // The following checks are not complete because the copy would be rejected
 1517                // if the encoder cannot remove required metadata.
 1518                // And if bsf is used, we must already be using copy codec.
 01519                switch (ShouldRemoveDynamicHdrMetadata(state))
 1520                {
 1521                    default:
 1522                    case DynamicHdrMetadataRemovalPlan.None:
 1523                        break;
 1524                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01525                        filter += _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.HevcMetadata
 01526                            ? ",hevc_metadata=remove_dovi=1"
 01527                            : ",dovi_rpu=strip=1";
 01528                        break;
 1529                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01530                        filter += ",hevc_metadata=remove_hdr10plus=1";
 1531                        break;
 1532                }
 1533
 01534                return filter;
 1535            }
 1536
 01537            if (IsAv1(stream))
 1538            {
 01539                switch (ShouldRemoveDynamicHdrMetadata(state))
 1540                {
 1541                    default:
 1542                    case DynamicHdrMetadataRemovalPlan.None:
 01543                        return null;
 1544                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01545                        return _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.Av1MetadataRemo
 01546                            ? "-bsf:v av1_metadata=remove_dovi=1"
 01547                            : "-bsf:v dovi_rpu=strip=1";
 1548                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01549                        return "-bsf:v av1_metadata=remove_hdr10plus=1";
 1550                }
 1551            }
 1552
 01553            return null;
 1554        }
 1555
 1556        public string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceConta
 1557        {
 121558            var filters = new List<string>();
 1559
 121560            var noiseFilter = GetCopiedAudioTrimBsf(state);
 121561            if (!string.IsNullOrEmpty(noiseFilter))
 1562            {
 61563                filters.Add(noiseFilter);
 1564            }
 1565
 121566            var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
 1567
 1568            // Apply aac_adtstoasc bitstream filter when media source is in mpegts.
 121569            if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)
 121570                && (string.Equals(mediaSourceContainer, "ts", StringComparison.OrdinalIgnoreCase)
 121571                    || string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase)
 121572                    || string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase))
 121573                && IsAAC(state.AudioStream))
 1574            {
 91575                filters.Add("aac_adtstoasc");
 1576            }
 1577
 121578            return filters.Count == 0
 121579                ? string.Empty
 121580                : " -bsf:a " + string.Join(',', filters);
 1581        }
 1582
 1583        // When video is transcoded, accurate_seek (the default) trims video to the
 1584        // exact seek point via decoder-side frame discard. But stream-copied audio
 1585        // bypasses the decoder, so it starts from the nearest keyframe — potentially
 1586        // seconds before the target. Use the noise bsf to drop copied audio packets
 1587        // before the seek target, achieving the same trim precision without
 1588        // re-encoding. The noise bsf's drop= parameter requires ffmpeg >= 5.0.
 1589        // Important: make sure not to use it with wtv because it breaks seeking
 1590        private string GetCopiedAudioTrimBsf(EncodingJobInfo state)
 1591        {
 121592            if (state.TranscodingType is not TranscodingJobType.Hls
 121593                || !state.IsVideoRequest
 121594                || IsCopyCodec(state.OutputVideoCodec)
 121595                || !IsCopyCodec(state.OutputAudioCodec)
 121596                || string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase)
 121597                || _mediaEncoder.EncoderVersion < _minFFmpegNoiseBsfDrop)
 1598            {
 51599                return null;
 1600            }
 1601
 71602            var startTicks = state.BaseRequest.StartTimeTicks ?? 0;
 71603            if (startTicks <= 0)
 1604            {
 11605                return null;
 1606            }
 1607
 61608            var seekSeconds = startTicks / (double)TimeSpan.TicksPerSecond;
 61609            return string.Format(
 61610                CultureInfo.InvariantCulture,
 61611                "noise=drop='lt(pts*tb\\,{0:F3})'",
 61612                seekSeconds);
 1613        }
 1614
 1615        public static string GetSegmentFileExtension(string segmentContainer)
 1616        {
 121617            if (!string.IsNullOrWhiteSpace(segmentContainer))
 1618            {
 121619                return "." + segmentContainer;
 1620            }
 1621
 01622            return ".ts";
 1623        }
 1624
 1625        private string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
 1626        {
 01627            if (state.OutputVideoBitrate is null)
 1628            {
 01629                return string.Empty;
 1630            }
 1631
 01632            int bitrate = state.OutputVideoBitrate.Value;
 1633
 1634            // Bit rate under 1000k is not allowed in h264_qsv.
 01635            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 1636            {
 01637                bitrate = Math.Max(bitrate, 1000);
 1638            }
 1639
 1640            // Currently use the same buffer size for all non-QSV encoders.
 1641            // Use long arithmetic to prevent int32 overflow for very high bitrate values.
 01642            int bufsize = (int)Math.Min((long)bitrate * 2, int.MaxValue);
 1643
 01644            if (string.Equals(videoCodec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1645            {
 01646                return FormattableString.Invariant($" -b:v {bitrate} -bufsize {bufsize}");
 1647            }
 1648
 01649            if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)
 01650                || string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
 1651            {
 01652                return FormattableString.Invariant($" -maxrate {bitrate} -bufsize {bufsize}");
 1653            }
 1654
 01655            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01656                || string.Equals(videoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 01657                || string.Equals(videoCodec, "av1_qsv", StringComparison.OrdinalIgnoreCase))
 1658            {
 1659                // TODO: probe QSV encoders' capabilities and enable more tuning options
 1660                // See also https://github.com/intel/media-delivery/blob/master/doc/quality.rst
 1661
 1662                // Enable MacroBlock level bitrate control for better subjective visual quality
 01663                var mbbrcOpt = string.Empty;
 01664                if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01665                    || string.Equals(videoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 1666                {
 01667                    mbbrcOpt = " -mbbrc 1";
 1668                }
 1669
 1670                // Some less powerful H.264 HW decoders require strict CPB size
 1671                // So bufsize optimizations should not be applied to them
 01672                int factor = 2;
 01673                var codec = state.ActualOutputVideoCodec;
 01674                var level = state.GetRequestedLevel(codec);
 01675                if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)
 01676                    && double.TryParse(level, CultureInfo.InvariantCulture, out double requestedLevel)
 01677                    && requestedLevel < 51)
 1678                {
 01679                    factor = 1;
 1680                }
 1681
 1682                // Set (maxrate == bitrate + 1) to trigger VBR for better bitrate allocation
 1683                // Set (rc_init_occupancy == 2 * bitrate) and (bufsize == 4 * bitrate) to deal with drastic scene change
 1684                // Use long arithmetic and clamp to int.MaxValue to prevent int32 overflow
 1685                // (e.g. bitrate * 4 wraps to a negative value for bitrates above ~537 million)
 01686                int qsvMaxrate = (int)Math.Min((long)bitrate + 1, int.MaxValue);
 01687                int qsvInitOcc = (int)Math.Min((long)bitrate * 1 * factor, int.MaxValue);
 01688                int qsvBufsize = (int)Math.Min((long)bitrate * 2 * factor, int.MaxValue);
 1689
 01690                return FormattableString.Invariant($"{mbbrcOpt} -b:v {bitrate} -maxrate {qsvMaxrate} -rc_init_occupancy 
 1691            }
 1692
 01693            if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01694                || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase))
 1695            {
 1696                // Override the too high default qmin 18 in transcoding preset in legacy h26x_amf
 01697                return FormattableString.Invariant($" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsiz
 1698            }
 1699
 01700            if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01701                || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01702                || string.Equals(videoCodec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1703            {
 1704                // VBR in i965 driver may result in pixelated output.
 01705                if (_mediaEncoder.IsVaapiDeviceInteli965)
 1706                {
 01707                    return FormattableString.Invariant($" -rc_mode CBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsi
 1708                }
 1709
 01710                return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"
 1711            }
 1712
 01713            if (string.Equals(videoCodec, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 01714                || string.Equals(videoCodec, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase))
 1715            {
 1716                // The `maxrate` and `bufsize` options can potentially lead to performance regression
 1717                // and even encoder hangs, especially when the value is very high.
 01718                return FormattableString.Invariant($" -b:v {bitrate} -qmin -1 -qmax -1");
 1719            }
 1720
 01721            return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
 1722        }
 1723
 1724        private string GetEncoderParam(EncoderPreset? preset, EncoderPreset defaultPreset, EncodingOptions encodingOptio
 1725        {
 01726            var param = string.Empty;
 01727            var encoderPreset = preset ?? defaultPreset;
 01728            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
 1729            {
 01730                var presetString = encoderPreset switch
 01731                {
 01732                    EncoderPreset.auto => EncoderPreset.veryfast.ToString().ToLowerInvariant(),
 01733                    _ => encoderPreset.ToString().ToLowerInvariant()
 01734                };
 1735
 01736                param += " -preset " + presetString;
 1737
 01738                int encodeCrf = encodingOptions.H264Crf;
 01739                if (isLibX265)
 1740                {
 01741                    encodeCrf = encodingOptions.H265Crf;
 1742                }
 1743
 01744                if (encodeCrf >= 0 && encodeCrf <= 51)
 1745                {
 01746                    param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture);
 1747                }
 1748                else
 1749                {
 01750                    string defaultCrf = "23";
 01751                    if (isLibX265)
 1752                    {
 01753                        defaultCrf = "28";
 1754                    }
 1755
 01756                    param += " -crf " + defaultCrf;
 1757                }
 1758            }
 01759            else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1760            {
 1761                // Default to use the recommended preset 10.
 1762                // Omit presets < 5, which are too slow for on the fly encoding.
 1763                // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md
 01764                param += encoderPreset switch
 01765                {
 01766                    EncoderPreset.veryslow => " -preset 5",
 01767                    EncoderPreset.slower => " -preset 6",
 01768                    EncoderPreset.slow => " -preset 7",
 01769                    EncoderPreset.medium => " -preset 8",
 01770                    EncoderPreset.fast => " -preset 9",
 01771                    EncoderPreset.faster => " -preset 10",
 01772                    EncoderPreset.veryfast => " -preset 11",
 01773                    EncoderPreset.superfast => " -preset 12",
 01774                    EncoderPreset.ultrafast => " -preset 13",
 01775                    _ => " -preset 10"
 01776                };
 1777            }
 01778            else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01779                     || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01780                     || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1781            {
 1782                // -compression_level is not reliable on AMD.
 01783                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 1784                {
 01785                    param += encoderPreset switch
 01786                    {
 01787                        EncoderPreset.veryslow => " -compression_level 1",
 01788                        EncoderPreset.slower => " -compression_level 2",
 01789                        EncoderPreset.slow => " -compression_level 3",
 01790                        EncoderPreset.medium => " -compression_level 4",
 01791                        EncoderPreset.fast => " -compression_level 5",
 01792                        EncoderPreset.faster => " -compression_level 6",
 01793                        EncoderPreset.veryfast => " -compression_level 7",
 01794                        EncoderPreset.superfast => " -compression_level 7",
 01795                        EncoderPreset.ultrafast => " -compression_level 7",
 01796                        _ => string.Empty
 01797                    };
 1798                }
 1799            }
 01800            else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv)
 01801                     || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv)
 01802                     || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv)
 1803            {
 01804                EncoderPreset[] valid_presets = [EncoderPreset.veryslow, EncoderPreset.slower, EncoderPreset.slow, Encod
 1805
 01806                param += " -preset " + (valid_presets.Contains(encoderPreset) ? encoderPreset : EncoderPreset.veryfast).
 1807            }
 01808            else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
 01809                        || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_n
 01810                        || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase) // av1 (av1_nven
 01811            )
 1812            {
 01813                param += encoderPreset switch
 01814                {
 01815                    EncoderPreset.veryslow => " -preset p7",
 01816                    EncoderPreset.slower => " -preset p6",
 01817                    EncoderPreset.slow => " -preset p5",
 01818                    EncoderPreset.medium => " -preset p4",
 01819                    EncoderPreset.fast => " -preset p3",
 01820                    EncoderPreset.faster => " -preset p2",
 01821                    _ => " -preset p1"
 01822                };
 1823            }
 01824            else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf)
 01825                        || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf
 01826                        || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase) // av1 (av1_amf)
 01827            )
 1828            {
 01829                param += encoderPreset switch
 01830                {
 01831                    EncoderPreset.veryslow => " -quality quality",
 01832                    EncoderPreset.slower => " -quality quality",
 01833                    EncoderPreset.slow => " -quality quality",
 01834                    EncoderPreset.medium => " -quality balanced",
 01835                    _ => " -quality speed"
 01836                };
 1837
 01838                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01839                    || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1840                {
 01841                    param += " -header_insertion_mode gop";
 1842                }
 1843
 01844                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
 1845                {
 01846                    param += " -gops_per_idr 1";
 1847                }
 1848            }
 01849            else if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase) // h264 (h264_
 01850                        || string.Equals(videoEncoder, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase) // hevc 
 01851            )
 1852            {
 01853                param += encoderPreset switch
 01854                {
 01855                    EncoderPreset.veryslow => " -prio_speed 0",
 01856                    EncoderPreset.slower => " -prio_speed 0",
 01857                    EncoderPreset.slow => " -prio_speed 0",
 01858                    EncoderPreset.medium => " -prio_speed 0",
 01859                    _ => " -prio_speed 1"
 01860                };
 1861            }
 1862
 01863            return param;
 1864        }
 1865
 1866        public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
 1867        {
 01868            if (!double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel))
 1869            {
 01870                return null;
 1871            }
 1872
 01873            if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase))
 1874            {
 1875                // Transcode to level 5.3 (15) and lower for maximum compatibility.
 1876                // https://en.wikipedia.org/wiki/AV1#Levels
 01877                if (requestLevel < 0 || requestLevel >= 15)
 1878                {
 01879                    return "15";
 1880                }
 1881            }
 01882            else if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 01883                     || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
 1884            {
 1885                // Transcode to level 5.0 and lower for maximum compatibility.
 1886                // Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it.
 1887                // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
 1888                // MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880.
 01889                if (requestLevel < 0 || requestLevel >= 150)
 1890                {
 01891                    return "150";
 1892                }
 1893            }
 01894            else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
 1895            {
 1896                // Transcode to level 5.1 and lower for maximum compatibility.
 1897                // h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
 1898                // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
 01899                if (requestLevel < 0 || requestLevel >= 51)
 1900                {
 01901                    return "51";
 1902                }
 1903            }
 1904
 01905            return level;
 1906        }
 1907
 1908        /// <summary>
 1909        /// Gets the text subtitle param.
 1910        /// </summary>
 1911        /// <param name="state">The state.</param>
 1912        /// <param name="enableAlpha">Enable alpha processing.</param>
 1913        /// <param name="enableSub2video">Enable sub2video mode.</param>
 1914        /// <returns>System.String.</returns>
 1915        public string GetTextSubtitlesFilter(EncodingJobInfo state, bool enableAlpha, bool enableSub2video)
 1916        {
 01917            var seconds = Math.Round(TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds);
 1918
 1919            // hls always copies timestamps
 01920            var setPtsParam = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive
 01921                ? string.Empty
 01922                : string.Format(CultureInfo.InvariantCulture, ",setpts=PTS -{0}/TB", seconds);
 1923
 01924            var alphaParam = enableAlpha ? ":alpha=1" : string.Empty;
 01925            var sub2videoParam = enableSub2video ? ":sub2video=1" : string.Empty;
 1926
 01927            var fontPath = _pathManager.GetAttachmentFolderPath(state.MediaSource.Id);
 01928            var fontParam = fontPath is null
 01929                ? string.Empty
 01930                : string.Format(
 01931                    CultureInfo.InvariantCulture,
 01932                    ":fontsdir='{0}'",
 01933                    _mediaEncoder.EscapeSubtitleFilterPath(fontPath));
 1934
 01935            if (state.SubtitleStream.IsExternal)
 1936            {
 01937                var charsetParam = string.Empty;
 1938
 01939                if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
 1940                {
 01941                    var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
 01942                            state.SubtitleStream,
 01943                            state.SubtitleStream.Language,
 01944                            state.MediaSource,
 01945                            CancellationToken.None).GetAwaiter().GetResult();
 1946
 01947                    if (!string.IsNullOrEmpty(charenc))
 1948                    {
 01949                        charsetParam = ":charenc=" + charenc;
 1950                    }
 1951                }
 1952
 01953                return string.Format(
 01954                    CultureInfo.InvariantCulture,
 01955                    "subtitles=f='{0}'{1}{2}{3}{4}{5}",
 01956                    _mediaEncoder.EscapeSubtitleFilterPath(state.SubtitleStream.Path),
 01957                    charsetParam,
 01958                    alphaParam,
 01959                    sub2videoParam,
 01960                    fontParam,
 01961                    setPtsParam);
 1962            }
 1963
 01964            var subtitlePath = _subtitleEncoder.GetSubtitleFilePath(
 01965                    state.SubtitleStream,
 01966                    state.MediaSource,
 01967                    CancellationToken.None).GetAwaiter().GetResult();
 1968
 01969            return string.Format(
 01970                CultureInfo.InvariantCulture,
 01971                "subtitles=f='{0}'{1}{2}{3}{4}",
 01972                _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
 01973                alphaParam,
 01974                sub2videoParam,
 01975                fontParam,
 01976                setPtsParam);
 1977        }
 1978
 1979        public double? GetFramerateParam(EncodingJobInfo state)
 1980        {
 01981            var request = state.BaseRequest;
 1982
 01983            if (request.Framerate.HasValue)
 1984            {
 01985                return request.Framerate.Value;
 1986            }
 1987
 01988            var maxrate = request.MaxFramerate;
 1989
 01990            if (maxrate.HasValue && state.VideoStream is not null)
 1991            {
 01992                var contentRate = state.VideoStream.ReferenceFrameRate;
 1993
 01994                if (contentRate.HasValue && contentRate.Value > maxrate.Value)
 1995                {
 01996                    return maxrate;
 1997                }
 1998            }
 1999
 02000            return null;
 2001        }
 2002
 2003        public string GetHlsVideoKeyFrameArguments(
 2004            EncodingJobInfo state,
 2005            string codec,
 2006            int segmentLength,
 2007            bool isEventPlaylist,
 2008            int? startNumber)
 2009        {
 02010            var args = string.Empty;
 02011            var gopArg = string.Empty;
 2012
 02013            var keyFrameArg = string.Format(
 02014                CultureInfo.InvariantCulture,
 02015                " -force_key_frames:0 \"expr:gte(t,n_forced*{0})\"",
 02016                segmentLength);
 2017
 02018            var framerate = state.VideoStream?.RealFrameRate;
 02019            if (framerate.HasValue)
 2020            {
 2021                // This is to make sure keyframe interval is limited to our segment,
 2022                // as forcing keyframes is not enough.
 2023                // Example: we encoded half of desired length, then codec detected
 2024                // scene cut and inserted a keyframe; next forced keyframe would
 2025                // be created outside of segment, which breaks seeking.
 02026                gopArg = string.Format(
 02027                    CultureInfo.InvariantCulture,
 02028                    " -g:v:0 {0} -keyint_min:v:0 {0}",
 02029                    Math.Ceiling(segmentLength * framerate.Value));
 2030            }
 2031
 2032            // Unable to force key frames using these encoders, set key frames by GOP.
 02033            if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02034                || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02035                || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02036                || string.Equals(codec, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 02037                || string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 02038                || string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 02039                || string.Equals(codec, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase)
 02040                || string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 02041                || string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 02042                || string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase)
 02043                || string.Equals(codec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 2044            {
 02045                args += gopArg;
 2046            }
 02047            else if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase)
 02048                     || string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)
 02049                     || string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02050                     || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02051                     || string.Equals(codec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 2052            {
 02053                args += keyFrameArg;
 2054
 2055                // prevent the libx264 from post processing to break the set keyframe.
 02056                if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase))
 2057                {
 02058                    args += " -sc_threshold:v:0 0";
 2059                }
 2060            }
 2061            else
 2062            {
 02063                args += keyFrameArg + gopArg;
 2064            }
 2065
 2066            // The in-band Parameter Sets generated by the AMD HEVC VA-API encoder is inconsistent
 2067            // with the extradata generated by ffmpeg, causing decoding failures when using hvc1.
 02068            if (string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02069                && _mediaEncoder.IsVaapiDeviceAmd)
 2070            {
 2071                // Extracting the extradata from the in-band PS to bypass the issue.
 2072                // This can be removed once the issue is resolved in libva or Mesa.
 2073                // Transcoding is unavoidable here, so using BSF will not conflict with BSF in remuxing.
 02074                args += " -flags:v -global_header -bsf:v extract_extradata=remove=0";
 2075            }
 2076
 02077            return args;
 2078        }
 2079
 2080        /// <summary>
 2081        /// Gets the video bitrate to specify on the command line.
 2082        /// </summary>
 2083        /// <param name="state">Encoding state.</param>
 2084        /// <param name="videoEncoder">Video encoder to use.</param>
 2085        /// <param name="encodingOptions">Encoding options.</param>
 2086        /// <param name="defaultPreset">Default present to use for encoding.</param>
 2087        /// <returns>Video bitrate.</returns>
 2088        public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, 
 2089        {
 02090            var param = string.Empty;
 2091
 2092            // Tutorials: Enable Intel GuC / HuC firmware loading for Low Power Encoding.
 2093            // https://01.org/group/43/downloads/firmware
 2094            // https://wiki.archlinux.org/title/intel_graphics#Enable_GuC_/_HuC_firmware_loading
 2095            // Intel Low Power Encoding can save unnecessary CPU-GPU synchronization,
 2096            // which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
 02097            var intelLowPowerHwEncoding = false;
 2098
 2099            // Workaround for linux 5.18 to 6.1.3 i915 hang at cost of performance.
 2100            // https://github.com/intel/media-driver/issues/1456
 02101            var enableWaFori915Hang = false;
 2102
 02103            var hardwareAccelerationType = encodingOptions.HardwareAccelerationType;
 2104
 02105            if (hardwareAccelerationType == HardwareAccelerationType.vaapi)
 2106            {
 02107                var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
 2108
 02109                if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
 2110                {
 02111                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder && isIntelVaapiDriver;
 2112                }
 02113                else if (string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
 2114                {
 02115                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder && isIntelVaapiDriver;
 2116                }
 2117            }
 02118            else if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 2119            {
 02120                if (OperatingSystem.IsLinux())
 2121                {
 02122                    var ver = Environment.OSVersion.Version;
 02123                    var isFixedKernel60 = ver.Major == 6 && ver.Minor == 0 && ver >= _minFixedKernel60i915Hang;
 02124                    var isUnaffectedKernel = ver < _minKerneli915Hang || ver > _maxKerneli915Hang;
 2125
 02126                    if (!(isUnaffectedKernel || isFixedKernel60))
 2127                    {
 02128                        var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
 02129                        var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
 02130                                             || vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 02131                        var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
 02132                            && IsVaapiSupported(state)
 02133                            && IsOpenclFullSupported()
 02134                            && !IsIntelVppTonemapAvailable(state, encodingOptions)
 02135                            && IsHwTonemapAvailable(state, encodingOptions);
 2136
 02137                        enableWaFori915Hang = isIntelDecoder && doOclTonemap;
 2138                    }
 2139                }
 2140
 02141                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 2142                {
 02143                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder;
 2144                }
 02145                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2146                {
 02147                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder;
 2148                }
 2149                else
 2150                {
 02151                    enableWaFori915Hang = false;
 2152                }
 2153            }
 2154
 02155            if (intelLowPowerHwEncoding)
 2156            {
 02157                param += " -low_power 1";
 2158            }
 2159
 02160            if (enableWaFori915Hang)
 2161            {
 02162                param += " -async_depth 1";
 2163            }
 2164
 02165            var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
 02166            var encodingPreset = encodingOptions.EncoderPreset;
 2167
 02168            param += GetEncoderParam(encodingPreset, defaultPreset, encodingOptions, videoEncoder, isLibX265);
 02169            param += GetVideoBitrateParam(state, videoEncoder);
 2170
 02171            var framerate = GetFramerateParam(state);
 02172            if (framerate.HasValue)
 2173            {
 02174                param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(CultureInfo.Inv
 2175            }
 2176
 02177            var targetVideoCodec = state.ActualOutputVideoCodec;
 02178            if (string.Equals(targetVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
 02179                || string.Equals(targetVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
 2180            {
 02181                targetVideoCodec = "hevc";
 2182            }
 2183
 02184            var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
 02185            profile = WhiteSpaceRegex().Replace(profile, string.Empty).ToLowerInvariant();
 2186
 02187            var videoProfiles = Array.Empty<string>();
 02188            if (string.Equals("h264", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2189            {
 02190                videoProfiles = _videoProfilesH264;
 2191            }
 02192            else if (string.Equals("hevc", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2193            {
 02194                videoProfiles = _videoProfilesH265;
 2195            }
 02196            else if (string.Equals("av1", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2197            {
 02198                videoProfiles = _videoProfilesAv1;
 2199            }
 2200
 02201            if (!videoProfiles.Contains(profile, StringComparison.OrdinalIgnoreCase))
 2202            {
 02203                profile = string.Empty;
 2204            }
 2205
 2206            // We only transcode to HEVC 8-bit for now, force Main Profile.
 02207            if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)
 02208                || profile.Contains("mainstill", StringComparison.OrdinalIgnoreCase))
 2209            {
 02210                profile = "main";
 2211            }
 2212
 2213            // Extended Profile is not supported by any known h264 encoders, force Main Profile.
 02214            if (profile.Contains("extended", StringComparison.OrdinalIgnoreCase))
 2215            {
 02216                profile = "main";
 2217            }
 2218
 2219            // Only libx264 support encoding H264 High 10 Profile, otherwise force High Profile.
 02220            if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02221                && profile.Contains("high10", StringComparison.OrdinalIgnoreCase))
 2222            {
 02223                profile = "high";
 2224            }
 2225
 2226            // We only need Main profile of AV1 encoders.
 02227            if (videoEncoder.Contains("av1", StringComparison.OrdinalIgnoreCase)
 02228                && (profile.Contains("high", StringComparison.OrdinalIgnoreCase)
 02229                    || profile.Contains("professional", StringComparison.OrdinalIgnoreCase)))
 2230            {
 02231                profile = "main";
 2232            }
 2233
 2234            // h264_vaapi does not support Baseline profile, force Constrained Baseline in this case,
 2235            // which is compatible (and ugly).
 02236            if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02237                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2238            {
 02239                profile = "constrained_baseline";
 2240            }
 2241
 2242            // libx264, h264_{qsv,nvenc,rkmpp} does not support Constrained Baseline profile, force Baseline in this cas
 02243            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02244                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02245                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02246                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02247                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2248            {
 02249                profile = "baseline";
 2250            }
 2251
 2252            // libx264, h264_{qsv,nvenc,vaapi,rkmpp} does not support Constrained High profile, force High in this case.
 02253            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02254                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02255                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02256                 || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02257                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02258                && profile.Contains("high", StringComparison.OrdinalIgnoreCase))
 2259            {
 02260                profile = "high";
 2261            }
 2262
 02263            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02264                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2265            {
 02266                profile = "constrained_baseline";
 2267            }
 2268
 02269            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02270                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2271            {
 02272                profile = "constrained_high";
 2273            }
 2274
 02275            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02276                && profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase))
 2277            {
 02278                profile = "constrained_baseline";
 2279            }
 2280
 02281            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02282                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2283            {
 02284                profile = "constrained_high";
 2285            }
 2286
 02287            if (!string.IsNullOrEmpty(profile))
 2288            {
 2289                // Currently there's no profile option in av1_nvenc encoder
 02290                if (!(string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 02291                      || string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)))
 2292                {
 02293                    param += " -profile:v:0 " + profile;
 2294                }
 2295            }
 2296
 02297            var level = NormalizeTranscodingLevel(state, state.GetRequestedLevel(targetVideoCodec));
 2298
 02299            if (!string.IsNullOrEmpty(level))
 2300            {
 2301                // libx264, QSV, AMF can adjust the given level to match the output.
 02302                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02303                    || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2304                {
 02305                    param += " -level " + level;
 2306                }
 02307                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2308                {
 2309                    // hevc_qsv use -level 51 instead of -level 153.
 02310                    if (double.TryParse(level, CultureInfo.InvariantCulture, out double hevcLevel))
 2311                    {
 02312                        param += " -level " + (hevcLevel / 3);
 2313                    }
 2314                }
 02315                else if (string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 02316                         || string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 2317                {
 2318                    // libsvtav1 and av1_qsv use -level 60 instead of -level 16
 2319                    // https://aomedia.org/av1/specification/annex-a/
 02320                    if (int.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out int av1Level))
 2321                    {
 02322                        var x = 2 + (av1Level >> 2);
 02323                        var y = av1Level & 3;
 02324                        var res = (x * 10) + y;
 02325                        param += " -level " + res;
 2326                    }
 2327                }
 02328                else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02329                         || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 02330                         || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 2331                {
 02332                    param += " -level " + level;
 2333                }
 02334                else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02335                         || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 02336                         || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase))
 2337                {
 2338                    // level option may cause NVENC to fail.
 2339                    // NVENC cannot adjust the given level, just throw an error.
 2340                }
 02341                else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02342                         || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02343                         || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 2344                {
 2345                    // level option may cause corrupted frames on AMD VAAPI.
 02346                    if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 2347                    {
 02348                        param += " -level " + level;
 2349                    }
 2350                }
 02351                else if (string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 02352                         || string.Equals(videoEncoder, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase))
 2353                {
 02354                    param += " -level " + level;
 2355                }
 02356                else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2357                {
 02358                    param += " -level " + level;
 2359                }
 2360            }
 2361
 02362            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2363            {
 02364                param += " -x264opts:0 subme=0:me_range=16:rc_lookahead=10:me=hex:open_gop=0";
 2365            }
 2366
 02367            if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2368            {
 2369                // libx265 only accept level option in -x265-params.
 2370                // level option may cause libx265 to fail.
 2371                // libx265 cannot adjust the given level, just throw an error.
 02372                param += " -x265-params:0 no-scenecut=1:no-open-gop=1:no-info=1";
 2373
 02374                if (encodingOptions.EncoderPreset < EncoderPreset.ultrafast)
 2375                {
 2376                    // The following params are slower than the ultrafast preset, don't use when ultrafast is selected.
 02377                    param += ":subme=3:merange=25:rc-lookahead=10:me=star:ctu=32:max-tu-size=32:min-cu-size=16:rskip=2:r
 2378                }
 2379            }
 2380
 02381            if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)
 02382                && _mediaEncoder.EncoderVersion >= _minFFmpegSvtAv1Params)
 2383            {
 02384                param += " -svtav1-params:0 rc=1:tune=0:film-grain=0:enable-overlays=1:enable-tf=0";
 2385            }
 2386
 2387            /* Access unit too large: 8192 < 20880 error */
 02388            if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) ||
 02389                 string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) &&
 02390                 _mediaEncoder.EncoderVersion >= _minFFmpegVaapiH26xEncA53CcSei)
 2391            {
 02392                param += " -sei -a53_cc";
 2393            }
 2394
 02395            return param;
 2396        }
 2397
 2398        public bool CanStreamCopyVideo(EncodingJobInfo state, MediaStream videoStream)
 2399        {
 02400            var request = state.BaseRequest;
 2401
 02402            if (!request.AllowVideoStreamCopy)
 2403            {
 02404                return false;
 2405            }
 2406
 02407            if (videoStream.IsInterlaced
 02408                && state.DeInterlace(videoStream.Codec, false))
 2409            {
 02410                return false;
 2411            }
 2412
 02413            if (videoStream.IsAnamorphic ?? false)
 2414            {
 02415                if (request.RequireNonAnamorphic)
 2416                {
 02417                    return false;
 2418                }
 2419            }
 2420
 2421            // Can't stream copy if we're burning in subtitles
 02422            if (request.SubtitleStreamIndex.HasValue
 02423                && request.SubtitleStreamIndex.Value >= 0
 02424                && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
 2425            {
 02426                return false;
 2427            }
 2428
 02429            if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 02430                && videoStream.IsAVC.HasValue
 02431                && !videoStream.IsAVC.Value
 02432                && request.RequireAvc)
 2433            {
 02434                return false;
 2435            }
 2436
 2437            // Source and target codecs must match
 02438            if (string.IsNullOrEmpty(videoStream.Codec)
 02439                || (state.SupportedVideoCodecs.Length != 0
 02440                    && !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparison.OrdinalIgnoreCase)))
 2441            {
 02442                return false;
 2443            }
 2444
 02445            var requestedProfiles = state.GetRequestedProfiles(videoStream.Codec);
 2446
 2447            // If client is requesting a specific video profile, it must match the source
 02448            if (requestedProfiles.Length > 0)
 2449            {
 02450                if (string.IsNullOrEmpty(videoStream.Profile))
 2451                {
 2452                    // return false;
 2453                }
 2454
 02455                var requestedProfile = requestedProfiles[0];
 2456                // strip spaces because they may be stripped out on the query string as well
 02457                if (!string.IsNullOrEmpty(videoStream.Profile)
 02458                    && !requestedProfiles.Contains(videoStream.Profile.Replace(" ", string.Empty, StringComparison.Ordin
 2459                {
 02460                    var currentScore = GetVideoProfileScore(videoStream.Codec, videoStream.Profile);
 02461                    var requestedScore = GetVideoProfileScore(videoStream.Codec, requestedProfile);
 2462
 02463                    if (currentScore == -1 || currentScore > requestedScore)
 2464                    {
 02465                        return false;
 2466                    }
 2467                }
 2468            }
 2469
 02470            var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
 02471            if (requestedRangeTypes.Length > 0)
 2472            {
 02473                if (videoStream.VideoRangeType == VideoRangeType.Unknown)
 2474                {
 02475                    return false;
 2476                }
 2477
 2478                // DOVIWithHDR10 should be compatible with HDR10 supporting players. Same goes with HLG and of course SD
 02479                var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ord
 02480                var requestHasHLG = requestedRangeTypes.Contains(VideoRangeType.HLG.ToString(), StringComparison.Ordinal
 02481                var requestHasSDR = requestedRangeTypes.Contains(VideoRangeType.SDR.ToString(), StringComparison.Ordinal
 02482                var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.Ordin
 2483
 2484                // If SDR is the only supported range, we should not copy any of the HDR streams.
 2485                // All the following copy check assumes at least one HDR format is supported.
 02486                if (requestedRangeTypes.Length == 1 && requestHasSDR && videoStream.VideoRangeType != VideoRangeType.SDR
 2487                {
 02488                    return false;
 2489                }
 2490
 2491                // If the client does not support DOVI and the video stream is DOVI without fallback, we should not copy
 02492                if (!requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVI)
 2493                {
 02494                    return false;
 2495                }
 2496
 02497                if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreC
 02498                     && !((requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10)
 02499                            || (requestHasHLG && videoStream.VideoRangeType == VideoRangeType.DOVIWithHLG)
 02500                            || (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)
 02501                            || (requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.HDR10Plus)))
 2502                {
 2503                    // If the video stream is in HDR10+ or a static HDR format, don't allow copy if the client does not 
 02504                    if (videoStream.VideoRangeType is VideoRangeType.HDR10Plus or VideoRangeType.HDR10 or VideoRangeType
 2505                    {
 02506                        return false;
 2507                    }
 2508
 2509                    // Check complicated cases where we need to remove dynamic metadata
 2510                    // Conservatively refuse to copy if the encoder can't remove dynamic metadata,
 2511                    // but a removal is required for compatability reasons.
 02512                    var dynamicHdrMetadataRemovalPlan = ShouldRemoveDynamicHdrMetadata(state);
 02513                    if (!CanEncoderRemoveDynamicHdrMetadata(dynamicHdrMetadataRemovalPlan, videoStream))
 2514                    {
 02515                        return false;
 2516                    }
 2517                }
 2518            }
 2519
 02520            var requestedRotations = state.GetRequestedRotations(videoStream.Codec);
 02521            if (requestedRotations.Length > 0)
 2522            {
 02523                var rotation = state.VideoStream?.Rotation ?? 0;
 02524                if (rotation != 0
 02525                    && !requestedRotations.Contains(rotation.ToString(CultureInfo.InvariantCulture), StringComparison.Or
 2526                {
 02527                    return false;
 2528                }
 2529            }
 2530
 2531            // Video width must fall within requested value
 02532            if (request.MaxWidth.HasValue
 02533                && (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
 2534            {
 02535                return false;
 2536            }
 2537
 2538            // Video height must fall within requested value
 02539            if (request.MaxHeight.HasValue
 02540                && (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value))
 2541            {
 02542                return false;
 2543            }
 2544
 2545            // Video framerate must fall within requested value
 02546            var requestedFramerate = request.MaxFramerate ?? request.Framerate;
 02547            if (requestedFramerate.HasValue)
 2548            {
 02549                var videoFrameRate = videoStream.ReferenceFrameRate;
 2550
 2551                // Add a little tolerance to the framerate check because some videos might record a framerate
 2552                // that is slightly greater than the intended framerate, but the device can still play it correctly.
 2553                // 0.05 fps tolerance should be safe enough.
 02554                if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value + 0.05f)
 2555                {
 02556                    return false;
 2557                }
 2558            }
 2559
 2560            // Video bitrate must fall within requested value
 02561            if (request.VideoBitRate.HasValue
 02562                && (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value))
 2563            {
 2564                // For LiveTV that has no bitrate, let's try copy if other conditions are met
 02565                if (string.IsNullOrWhiteSpace(request.LiveStreamId) || videoStream.BitRate.HasValue)
 2566                {
 02567                    return false;
 2568                }
 2569            }
 2570
 02571            var maxBitDepth = state.GetRequestedVideoBitDepth(videoStream.Codec);
 02572            if (maxBitDepth.HasValue)
 2573            {
 02574                if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > maxBitDepth.Value)
 2575                {
 02576                    return false;
 2577                }
 2578            }
 2579
 02580            var maxRefFrames = state.GetRequestedMaxRefFrames(videoStream.Codec);
 02581            if (maxRefFrames.HasValue
 02582                && videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > maxRefFrames.Value)
 2583            {
 02584                return false;
 2585            }
 2586
 2587            // If a specific level was requested, the source must match or be less than
 02588            var level = state.GetRequestedLevel(videoStream.Codec);
 02589            if (double.TryParse(level, CultureInfo.InvariantCulture, out var requestLevel))
 2590            {
 02591                if (!videoStream.Level.HasValue)
 2592                {
 2593                    // return false;
 2594                }
 2595
 02596                if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
 2597                {
 02598                    return false;
 2599                }
 2600            }
 2601
 02602            if (string.Equals(state.InputContainer, "avi", StringComparison.OrdinalIgnoreCase)
 02603                && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)
 02604                && !(videoStream.IsAVC ?? false))
 2605            {
 2606                // see Coach S01E01 - Kelly and the Professor(0).avi
 02607                return false;
 2608            }
 2609
 02610            return true;
 2611        }
 2612
 2613        public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, IEnumerable<string> supportedAudi
 2614        {
 02615            var request = state.BaseRequest;
 2616
 02617            if (!request.AllowAudioStreamCopy)
 2618            {
 02619                return false;
 2620            }
 2621
 02622            var maxBitDepth = state.GetRequestedAudioBitDepth(audioStream.Codec);
 02623            if (maxBitDepth.HasValue
 02624                && audioStream.BitDepth.HasValue
 02625                && audioStream.BitDepth.Value > maxBitDepth.Value)
 2626            {
 02627                return false;
 2628            }
 2629
 2630            // Source and target codecs must match
 02631            if (string.IsNullOrEmpty(audioStream.Codec)
 02632                || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparison.OrdinalIgnoreCase))
 2633            {
 02634                return false;
 2635            }
 2636
 2637            // Channels must fall within requested value
 02638            var channels = state.GetRequestedAudioChannels(audioStream.Codec);
 02639            if (channels.HasValue)
 2640            {
 02641                if (!audioStream.Channels.HasValue || audioStream.Channels.Value <= 0)
 2642                {
 02643                    return false;
 2644                }
 2645
 02646                if (audioStream.Channels.Value > channels.Value)
 2647                {
 02648                    return false;
 2649                }
 2650            }
 2651
 2652            // Sample rate must fall within requested value
 02653            if (request.AudioSampleRate.HasValue)
 2654            {
 02655                if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value <= 0)
 2656                {
 02657                    return false;
 2658                }
 2659
 02660                if (audioStream.SampleRate.Value > request.AudioSampleRate.Value)
 2661                {
 02662                    return false;
 2663                }
 2664            }
 2665
 2666            // Audio bitrate must fall within requested value
 02667            if (request.AudioBitRate.HasValue
 02668                && audioStream.BitRate.HasValue
 02669                && audioStream.BitRate.Value > request.AudioBitRate.Value)
 2670            {
 02671                return false;
 2672            }
 2673
 02674            return request.EnableAutoStreamCopy;
 2675        }
 2676
 2677        public int GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideo
 2678        {
 02679            var bitrate = request.VideoBitRate;
 2680
 02681            if (videoStream is not null)
 2682            {
 02683                var isUpscaling = request.Height.HasValue
 02684                    && videoStream.Height.HasValue
 02685                    && request.Height.Value > videoStream.Height.Value
 02686                    && request.Width.HasValue
 02687                    && videoStream.Width.HasValue
 02688                    && request.Width.Value > videoStream.Width.Value;
 2689
 2690                // Don't allow bitrate increases unless upscaling
 02691                if (!isUpscaling && bitrate.HasValue && videoStream.BitRate.HasValue)
 2692                {
 02693                    bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value);
 2694                }
 2695
 02696                if (bitrate.HasValue)
 2697                {
 02698                    var inputVideoCodec = videoStream.Codec;
 02699                    bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
 2700
 2701                    // If a max bitrate was requested, don't let the scaled bitrate exceed it
 02702                    if (request.VideoBitRate.HasValue)
 2703                    {
 02704                        bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
 2705                    }
 2706                }
 2707            }
 2708
 2709            // Cap the max target bitrate to 400 Mbps.
 2710            // No consumer or professional hardware transcode target exceeds this value
 2711            // (Intel QSV tops out at ~300 Mbps for H.264; HEVC High Tier Level 5.x is ~240 Mbps).
 2712            // Without this cap, plugin-provided MPEG-TS streams with no usable bitrate metadata
 2713            // can produce unreasonably large -bufsize/-maxrate values for the encoder.
 2714            // Note: the existing FallbackMaxStreamingBitrate mechanism (default 30 Mbps) only
 2715            // applies when a LiveStreamId is set (M3U/HDHR sources). Plugin streams and other
 2716            // sources that bypass the LiveTV pipeline are not covered by it.
 2717            const int MaxSaneBitrate = 400_000_000; // 400 Mbps
 02718            return Math.Min(bitrate ?? 0, MaxSaneBitrate);
 2719        }
 2720
 2721        private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
 2722        {
 2723            // these values were chosen from testing to improve low bitrate streams
 02724            if (sourceBitrate <= 2000000)
 2725            {
 02726                sourceBitrate = Convert.ToInt32(sourceBitrate * 2.5);
 2727            }
 02728            else if (sourceBitrate <= 3000000)
 2729            {
 02730                sourceBitrate *= 2;
 2731            }
 2732
 02733            var bitrate = Math.Min(sourceBitrate, requestedBitrate);
 2734
 02735            return bitrate;
 2736        }
 2737
 2738        private static double GetVideoBitrateScaleFactor(string codec)
 2739        {
 2740            // hevc & vp9 - 40% more efficient than h.264
 02741            if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 02742                || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
 02743                || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
 2744            {
 02745                return .6;
 2746            }
 2747
 2748            // av1 - 50% more efficient than h.264
 02749            if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 2750            {
 02751                return .5;
 2752            }
 2753
 02754            return 1;
 2755        }
 2756
 2757        public static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
 2758        {
 02759            var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
 02760            var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
 2761
 2762            // Don't scale the real bitrate lower than the requested bitrate
 02763            var scaleFactor = Math.Max(outputScaleFactor / inputScaleFactor, 1);
 2764
 02765            if (bitrate <= 500000)
 2766            {
 02767                scaleFactor = Math.Max(scaleFactor, 4);
 2768            }
 02769            else if (bitrate <= 1000000)
 2770            {
 02771                scaleFactor = Math.Max(scaleFactor, 3);
 2772            }
 02773            else if (bitrate <= 2000000)
 2774            {
 02775                scaleFactor = Math.Max(scaleFactor, 2.5);
 2776            }
 02777            else if (bitrate <= 3000000)
 2778            {
 02779                scaleFactor = Math.Max(scaleFactor, 2);
 2780            }
 02781            else if (bitrate >= 30000000)
 2782            {
 2783                // Don't scale beyond 30Mbps, it is hardly visually noticeable for most codecs with our prefer speed enc
 2784                // and will cause extremely high bitrate to be used for av1->h264 transcoding that will overload clients
 02785                scaleFactor = 1;
 2786            }
 2787
 02788            return Convert.ToInt32(scaleFactor * bitrate);
 2789        }
 2790
 2791        public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChanne
 2792        {
 02793            return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels);
 2794        }
 2795
 2796        public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudio
 2797        {
 02798            if (audioStream is null)
 2799            {
 02800                return null;
 2801            }
 2802
 02803            var inputChannels = audioStream.Channels ?? 0;
 02804            var outputChannels = outputAudioChannels ?? 0;
 02805            var bitrate = audioBitRate ?? int.MaxValue;
 2806
 02807            if (string.IsNullOrEmpty(audioCodec)
 02808                || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
 02809                || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
 02810                || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
 02811                || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
 02812                || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
 02813                || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
 2814            {
 2815#pragma warning disable SA1008
 02816                return (inputChannels, outputChannels) switch
 02817                {
 02818                    ( >= 6, >= 6 or 0) => Math.Min(640000, bitrate),
 02819                    ( > 0, > 0) => Math.Min(outputChannels * 128000, bitrate),
 02820                    ( > 0, _) => Math.Min(inputChannels * 128000, bitrate),
 02821                    (_, _) => Math.Min(384000, bitrate)
 02822                };
 2823#pragma warning restore SA1008
 2824            }
 2825
 02826            if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
 02827                || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
 2828            {
 2829#pragma warning disable SA1008
 02830                return (inputChannels, outputChannels) switch
 02831                {
 02832                    ( >= 6, >= 6 or 0) => Math.Min(768000, bitrate),
 02833                    ( > 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
 02834                    ( > 0, _) => Math.Min(inputChannels * 136000, bitrate),
 02835                    (_, _) => Math.Min(672000, bitrate)
 02836                };
 2837#pragma warning restore SA1008
 2838            }
 2839
 2840            // Empty bitrate area is not allow on iOS
 2841            // Default audio bitrate to 128K per channel if we don't have codec specific defaults
 2842            // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
 02843            return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
 2844        }
 2845
 2846        public string GetAudioVbrModeParam(string encoder, int bitrate, int channels)
 2847        {
 02848            var bitratePerChannel = bitrate / Math.Max(channels, 1);
 02849            if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
 2850            {
 02851                return " -vbr:a " + bitratePerChannel switch
 02852                {
 02853                    < 32000 => "1",
 02854                    < 48000 => "2",
 02855                    < 64000 => "3",
 02856                    < 96000 => "4",
 02857                    _ => "5"
 02858                };
 2859            }
 2860
 02861            if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase))
 2862            {
 2863                // lame's VBR is only good for a certain bitrate range
 2864                // For very low and very high bitrate, use abr mode
 02865                if (bitratePerChannel is < 122500 and > 48000)
 2866                {
 02867                    return " -qscale:a " + bitratePerChannel switch
 02868                    {
 02869                        < 64000 => "6",
 02870                        < 88000 => "4",
 02871                        < 112000 => "2",
 02872                        _ => "0"
 02873                    };
 2874                }
 2875
 02876                return " -abr:a 1" + " -b:a " + bitrate;
 2877            }
 2878
 02879            if (string.Equals(encoder, "aac_at", StringComparison.OrdinalIgnoreCase))
 2880            {
 2881                // aac_at's CVBR mode
 02882                return " -aac_at_mode:a 2" + " -b:a " + bitrate;
 2883            }
 2884
 02885            if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase))
 2886            {
 02887                return " -qscale:a " + bitratePerChannel switch
 02888                {
 02889                    < 40000 => "0",
 02890                    < 56000 => "2",
 02891                    < 80000 => "4",
 02892                    < 112000 => "6",
 02893                    _ => "8"
 02894                };
 2895            }
 2896
 02897            return null;
 2898        }
 2899
 2900        public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
 2901        {
 02902            var channels = state.OutputAudioChannels;
 2903
 02904            var filters = new List<string>();
 2905
 02906            if (channels is 2 && state.AudioStream?.Channels is > 2)
 2907            {
 02908                var hasDownMixFilter = DownMixAlgorithmsHelper.AlgorithmFilterStrings.TryGetValue((encodingOptions.DownM
 02909                if (hasDownMixFilter)
 2910                {
 02911                    filters.Add(downMixFilterString);
 2912                }
 2913
 02914                if (!encodingOptions.DownMixAudioBoost.Equals(1))
 2915                {
 02916                    filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
 2917                }
 2918            }
 2919
 02920            var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
 02921            if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && ShouldEncodeSubtitle(st
 2922            {
 02923                var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds;
 2924
 02925                filters.Add(
 02926                    string.Format(
 02927                        CultureInfo.InvariantCulture,
 02928                        "asetpts=PTS-{0}/TB",
 02929                        Math.Round(seconds)));
 2930            }
 2931
 02932            if (filters.Count > 0)
 2933            {
 02934                return " -af \"" + string.Join(',', filters) + "\"";
 2935            }
 2936
 02937            return string.Empty;
 2938        }
 2939
 2940        /// <summary>
 2941        /// Gets the number of audio channels to specify on the command line.
 2942        /// </summary>
 2943        /// <param name="state">The state.</param>
 2944        /// <param name="audioStream">The audio stream.</param>
 2945        /// <param name="outputAudioCodec">The output audio codec.</param>
 2946        /// <returns>System.Nullable{System.Int32}.</returns>
 2947        public int? GetNumAudioChannelsParam(EncodingJobInfo state, MediaStream audioStream, string outputAudioCodec)
 2948        {
 02949            if (audioStream is null)
 2950            {
 02951                return null;
 2952            }
 2953
 02954            var request = state.BaseRequest;
 2955
 02956            var codec = outputAudioCodec ?? string.Empty;
 2957
 02958            int? resultChannels = state.GetRequestedAudioChannels(codec);
 2959
 02960            var inputChannels = audioStream.Channels;
 2961
 02962            if (inputChannels > 0)
 2963            {
 02964                resultChannels = inputChannels < resultChannels ? inputChannels : resultChannels ?? inputChannels;
 2965            }
 2966
 02967            var isTranscodingAudio = !IsCopyCodec(codec);
 2968
 02969            if (isTranscodingAudio)
 2970            {
 02971                var audioEncoder = GetAudioEncoder(state);
 02972                if (!_audioTranscodeChannelLookup.TryGetValue(audioEncoder, out var transcoderChannelLimit))
 2973                {
 2974                    // Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many c
 02975                    transcoderChannelLimit = 8;
 2976                }
 2977
 2978                // Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelL
 02979                resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? tr
 2980
 02981                if (request.TranscodingMaxAudioChannels < resultChannels)
 2982                {
 02983                    resultChannels = request.TranscodingMaxAudioChannels;
 2984                }
 2985
 2986                // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
 2987                // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_d
 02988                if (state.TranscodingType != TranscodingJobType.Progressive
 02989                    && ((resultChannels > 2 && resultChannels < 6) || resultChannels == 7))
 2990                {
 2991                    // We can let FFMpeg supply an extra LFE channel for 5ch and 7ch to make them 5.1 and 7.1
 02992                    if (resultChannels == 5)
 2993                    {
 02994                        resultChannels = 6;
 2995                    }
 02996                    else if (resultChannels == 7)
 2997                    {
 02998                        resultChannels = 8;
 2999                    }
 3000                    else
 3001                    {
 3002                        // For other weird layout, just downmix to stereo for compatibility
 03003                        resultChannels = 2;
 3004                    }
 3005                }
 3006            }
 3007
 03008            return resultChannels;
 3009        }
 3010
 3011        /// <summary>
 3012        /// Enforces the resolution limit.
 3013        /// </summary>
 3014        /// <param name="state">The state.</param>
 3015        public void EnforceResolutionLimit(EncodingJobInfo state)
 3016        {
 03017            var videoRequest = state.BaseRequest;
 3018
 3019            // Switch the incoming params to be ceilings rather than fixed values
 03020            videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
 03021            videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
 3022
 03023            videoRequest.Width = null;
 03024            videoRequest.Height = null;
 03025        }
 3026
 3027        /// <summary>
 3028        /// Gets the fast seek command line parameter.
 3029        /// </summary>
 3030        /// <param name="state">The state.</param>
 3031        /// <param name="options">The options.</param>
 3032        /// <param name="segmentContainer">Segment Container.</param>
 3033        /// <returns>System.String.</returns>
 3034        /// <value>The fast seek command line parameter.</value>
 3035        public string GetFastSeekCommandLineParameter(EncodingJobInfo state, EncodingOptions options, string segmentCont
 3036        {
 43037            var time = state.BaseRequest.StartTimeTicks ?? 0;
 43038            var maxTime = state.RunTimeTicks ?? 0;
 43039            var seekParam = string.Empty;
 3040
 43041            if (time > 0)
 3042            {
 3043                // For direct streaming/remuxing, HLS segments start at keyframes.
 3044                // However, ffmpeg will seek to previous keyframe when the exact frame time is the input
 3045                // Workaround this by adding 0.5s offset to the seeking time to get the exact keyframe on most videos.
 3046                // This will help subtitle syncing.
 03047                var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCod
 03048                var seekTick = isHlsRemuxing ? time + 5000000L : time;
 3049
 3050                // Seeking beyond EOF makes no sense in transcoding. Clamp the seekTick value to
 3051                // [0, RuntimeTicks - 5.0s], so that the muxer gets packets and avoid error codes.
 03052                if (maxTime > 0)
 3053                {
 03054                    seekTick = Math.Clamp(seekTick, 0, Math.Max(maxTime - 50000000L, 0));
 3055                }
 3056
 03057                seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(seekT
 3058            }
 3059
 43060            return seekParam;
 3061        }
 3062
 3063        /// <summary>
 3064        /// Gets the map args.
 3065        /// </summary>
 3066        /// <param name="state">The state.</param>
 3067        /// <returns>System.String.</returns>
 3068        public string GetMapArgs(EncodingJobInfo state)
 3069        {
 3070            // If we don't have known media info
 3071            // If input is video, use -sn to drop subtitles
 3072            // Otherwise just return empty
 73073            if (state.VideoStream is null && state.AudioStream is null)
 3074            {
 03075                return state.IsInputVideo ? "-sn" : string.Empty;
 3076            }
 3077
 3078            // We have media info, but we don't know the stream index
 73079            if (state.VideoStream is not null && state.VideoStream.Index == -1)
 3080            {
 03081                return "-sn";
 3082            }
 3083
 3084            // We have media info, but we don't know the stream index
 73085            if (state.AudioStream is not null && state.AudioStream.Index == -1)
 3086            {
 03087                return state.IsInputVideo ? "-sn" : string.Empty;
 3088            }
 3089
 73090            var args = string.Empty;
 3091
 73092            if (state.VideoStream is not null)
 3093            {
 73094                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 3095
 73096                args += string.Format(
 73097                    CultureInfo.InvariantCulture,
 73098                    "-map 0:{0}",
 73099                    videoStreamIndex);
 3100            }
 3101            else
 3102            {
 3103                // No known video stream
 03104                args += "-vn";
 3105            }
 3106
 73107            if (state.AudioStream is not null)
 3108            {
 73109                int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
 73110                if (state.AudioStream.IsExternal)
 3111                {
 03112                    bool hasExternalSubAsInput = NeedsExternalSubtitleMuxing(state);
 03113                    int externalAudioMapIndex = hasExternalSubAsInput ? 2 : 1;
 3114
 03115                    args += string.Format(
 03116                        CultureInfo.InvariantCulture,
 03117                        " -map {0}:{1}",
 03118                        externalAudioMapIndex,
 03119                        audioStreamIndex);
 3120                }
 3121                else
 3122                {
 73123                    args += string.Format(
 73124                        CultureInfo.InvariantCulture,
 73125                        " -map 0:{0}",
 73126                        audioStreamIndex);
 3127                }
 3128            }
 3129            else
 3130            {
 03131                args += " -map -0:a";
 3132            }
 3133
 73134            var subtitleMethod = state.SubtitleDeliveryMethod;
 73135            if (state.SubtitleStream is null || subtitleMethod == SubtitleDeliveryMethod.Hls)
 3136            {
 13137                args += " -map -0:s";
 3138            }
 63139            else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
 3140            {
 63141                if (state.SubtitleStream.IsExternal)
 3142                {
 3143                    // External subtitle file is added as second FFmpeg input.
 3144                    // For single-stream files (SRT/ASS/VTT) the in-file index is always 0.
 3145                    // For multi-stream containers (MKS) we count how many streams from
 3146                    // the same file appear before the selected one.
 43147                    var inFileIndex = state.MediaSource.MediaStreams
 43148                        .Where(s => string.Equals(s.Path, state.SubtitleStream.Path, StringComparison.Ordinal))
 43149                        .TakeWhile(s => s.Index != state.SubtitleStream.Index)
 43150                        .Count();
 3151
 43152                    args += string.Format(
 43153                        CultureInfo.InvariantCulture,
 43154                        " -map 1:{0}",
 43155                        inFileIndex);
 3156                }
 3157                else
 3158                {
 23159                    int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3160
 23161                    args += string.Format(
 23162                        CultureInfo.InvariantCulture,
 23163                        " -map 0:{0}",
 23164                        subtitleStreamIndex);
 3165                }
 3166            }
 03167            else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
 3168            {
 03169                int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3170
 03171                args += string.Format(
 03172                    CultureInfo.InvariantCulture,
 03173                    " -map 1:{0} -sn",
 03174                    externalSubtitleStreamIndex);
 3175            }
 3176
 73177            return args;
 3178        }
 3179
 3180        /// <summary>
 3181        /// Gets the negative map args by filters.
 3182        /// </summary>
 3183        /// <param name="state">The state.</param>
 3184        /// <param name="videoProcessFilters">The videoProcessFilters.</param>
 3185        /// <returns>System.String.</returns>
 3186        public string GetNegativeMapArgsByFilters(EncodingJobInfo state, string videoProcessFilters)
 3187        {
 03188            string args = string.Empty;
 3189
 3190            // http://ffmpeg.org/ffmpeg-all.html#toc-Complex-filtergraphs-1
 03191            if (state.VideoStream is not null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordina
 3192            {
 03193                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 3194
 03195                args += string.Format(
 03196                    CultureInfo.InvariantCulture,
 03197                    "-map -0:{0} ",
 03198                    videoStreamIndex);
 3199            }
 3200
 03201            return args;
 3202        }
 3203
 3204        /// <summary>
 3205        /// Determines which stream will be used for playback.
 3206        /// </summary>
 3207        /// <param name="allStream">All stream.</param>
 3208        /// <param name="desiredIndex">Index of the desired.</param>
 3209        /// <param name="type">The type.</param>
 3210        /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
 3211        /// <returns>MediaStream.</returns>
 3212        public MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, b
 3213        {
 03214            var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
 3215
 03216            if (desiredIndex.HasValue)
 3217            {
 03218                var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
 3219
 03220                if (stream is not null)
 3221                {
 03222                    return stream;
 3223                }
 3224            }
 3225
 03226            if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
 3227            {
 03228                return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
 03229                       streams.FirstOrDefault();
 3230            }
 3231
 3232            // Just return the first one
 03233            return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
 3234        }
 3235
 3236        public static (int? Width, int? Height) GetFixedOutputSize(
 3237            int? videoWidth,
 3238            int? videoHeight,
 3239            int? requestedWidth,
 3240            int? requestedHeight,
 3241            int? requestedMaxWidth,
 3242            int? requestedMaxHeight)
 3243        {
 03244            if (!videoWidth.HasValue && !requestedWidth.HasValue)
 3245            {
 03246                return (null, null);
 3247            }
 3248
 03249            if (!videoHeight.HasValue && !requestedHeight.HasValue)
 3250            {
 03251                return (null, null);
 3252            }
 3253
 03254            int inputWidth = Convert.ToInt32(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture);
 03255            int inputHeight = Convert.ToInt32(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture);
 03256            int outputWidth = requestedWidth ?? inputWidth;
 03257            int outputHeight = requestedHeight ?? inputHeight;
 3258
 3259            // Don't transcode video to bigger than 4k when using HW.
 03260            int maximumWidth = Math.Min(requestedMaxWidth ?? outputWidth, 4096);
 03261            int maximumHeight = Math.Min(requestedMaxHeight ?? outputHeight, 4096);
 3262
 03263            if (outputWidth > maximumWidth || outputHeight > maximumHeight)
 3264            {
 03265                var scaleW = (double)maximumWidth / outputWidth;
 03266                var scaleH = (double)maximumHeight / outputHeight;
 03267                var scale = Math.Min(scaleW, scaleH);
 03268                outputWidth = Math.Min(maximumWidth, Convert.ToInt32(outputWidth * scale));
 03269                outputHeight = Math.Min(maximumHeight, Convert.ToInt32(outputHeight * scale));
 3270            }
 3271
 03272            outputWidth = 2 * (outputWidth / 2);
 03273            outputHeight = 2 * (outputHeight / 2);
 3274
 03275            return (outputWidth, outputHeight);
 3276        }
 3277
 3278        public static bool IsScaleRatioSupported(
 3279            int? videoWidth,
 3280            int? videoHeight,
 3281            int? requestedWidth,
 3282            int? requestedHeight,
 3283            int? requestedMaxWidth,
 3284            int? requestedMaxHeight,
 3285            double? maxScaleRatio)
 3286        {
 03287            var (outWidth, outHeight) = GetFixedOutputSize(
 03288                videoWidth,
 03289                videoHeight,
 03290                requestedWidth,
 03291                requestedHeight,
 03292                requestedMaxWidth,
 03293                requestedMaxHeight);
 3294
 03295            if (!videoWidth.HasValue
 03296                 || !videoHeight.HasValue
 03297                 || !outWidth.HasValue
 03298                 || !outHeight.HasValue
 03299                 || !maxScaleRatio.HasValue
 03300                 || (maxScaleRatio.Value < 1.0f))
 3301            {
 03302                return false;
 3303            }
 3304
 03305            var minScaleRatio = 1.0f / maxScaleRatio;
 03306            var scaleRatioW = (double)outWidth / (double)videoWidth;
 03307            var scaleRatioH = (double)outHeight / (double)videoHeight;
 3308
 03309            if (scaleRatioW < minScaleRatio
 03310                || scaleRatioW > maxScaleRatio
 03311                || scaleRatioH < minScaleRatio
 03312                || scaleRatioH > maxScaleRatio)
 3313            {
 03314                return false;
 3315            }
 3316
 03317            return true;
 3318        }
 3319
 3320        public static string GetHwScaleFilter(
 3321            string hwScalePrefix,
 3322            string hwScaleSuffix,
 3323            string videoFormat,
 3324            bool swapOutputWandH,
 3325            int? videoWidth,
 3326            int? videoHeight,
 3327            int? requestedWidth,
 3328            int? requestedHeight,
 3329            int? requestedMaxWidth,
 3330            int? requestedMaxHeight)
 3331        {
 03332            var (outWidth, outHeight) = GetFixedOutputSize(
 03333                videoWidth,
 03334                videoHeight,
 03335                requestedWidth,
 03336                requestedHeight,
 03337                requestedMaxWidth,
 03338                requestedMaxHeight);
 3339
 03340            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03341            var isSizeFixed = !videoWidth.HasValue
 03342                || outWidth.Value != videoWidth.Value
 03343                || !videoHeight.HasValue
 03344                || outHeight.Value != videoHeight.Value;
 3345
 03346            var swpOutW = swapOutputWandH ? outHeight.Value : outWidth.Value;
 03347            var swpOutH = swapOutputWandH ? outWidth.Value : outHeight.Value;
 3348
 03349            var arg1 = isSizeFixed ? $"=w={swpOutW}:h={swpOutH}" : string.Empty;
 03350            var arg2 = isFormatFixed ? $"format={videoFormat}" : string.Empty;
 03351            if (isFormatFixed)
 3352            {
 03353                arg2 = (isSizeFixed ? ':' : '=') + arg2;
 3354            }
 3355
 03356            if (!string.IsNullOrEmpty(hwScaleSuffix) && (isSizeFixed || isFormatFixed))
 3357            {
 03358                return string.Format(
 03359                    CultureInfo.InvariantCulture,
 03360                    "{0}_{1}{2}{3}",
 03361                    hwScalePrefix ?? "scale",
 03362                    hwScaleSuffix,
 03363                    arg1,
 03364                    arg2);
 3365            }
 3366
 03367            return string.Empty;
 3368        }
 3369
 3370        public static string GetGraphicalSubPreProcessFilters(
 3371            int? videoWidth,
 3372            int? videoHeight,
 3373            int? subtitleWidth,
 3374            int? subtitleHeight,
 3375            int? requestedWidth,
 3376            int? requestedHeight,
 3377            int? requestedMaxWidth,
 3378            int? requestedMaxHeight)
 3379        {
 03380            var (outWidth, outHeight) = GetFixedOutputSize(
 03381                videoWidth,
 03382                videoHeight,
 03383                requestedWidth,
 03384                requestedHeight,
 03385                requestedMaxWidth,
 03386                requestedMaxHeight);
 3387
 03388            if (!outWidth.HasValue
 03389                || !outHeight.HasValue
 03390                || outWidth.Value <= 0
 03391                || outHeight.Value <= 0)
 3392            {
 03393                return string.Empty;
 3394            }
 3395
 3396            // Automatically add padding based on subtitle input
 03397            var filters = @"scale,scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:blac
 3398
 03399            if (subtitleWidth.HasValue
 03400                && subtitleHeight.HasValue
 03401                && subtitleWidth.Value > 0
 03402                && subtitleHeight.Value > 0)
 3403            {
 03404                var videoDar = (double)outWidth.Value / outHeight.Value;
 03405                var subtitleDar = (double)subtitleWidth.Value / subtitleHeight.Value;
 3406
 3407                // No need to add padding when DAR is the same -> 1080p PGSSUB on 2160p video
 03408                if (Math.Abs(videoDar - subtitleDar) < 0.01f)
 3409                {
 03410                    filters = @"scale,scale={0}:{1}:fast_bilinear";
 3411                }
 3412            }
 3413
 03414            return string.Format(
 03415                CultureInfo.InvariantCulture,
 03416                filters,
 03417                outWidth.Value,
 03418                outHeight.Value);
 3419        }
 3420
 3421        public static string GetAlphaSrcFilter(
 3422            EncodingJobInfo state,
 3423            int? videoWidth,
 3424            int? videoHeight,
 3425            int? requestedWidth,
 3426            int? requestedHeight,
 3427            int? requestedMaxWidth,
 3428            int? requestedMaxHeight,
 3429            float? framerate)
 3430        {
 03431            var reqTicks = state.BaseRequest.StartTimeTicks ?? 0;
 03432            var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture
 03433            var (outWidth, outHeight) = GetFixedOutputSize(
 03434                videoWidth,
 03435                videoHeight,
 03436                requestedWidth,
 03437                requestedHeight,
 03438                requestedMaxWidth,
 03439                requestedMaxHeight);
 3440
 03441            if (outWidth.HasValue && outHeight.HasValue)
 3442            {
 03443                return string.Format(
 03444                    CultureInfo.InvariantCulture,
 03445                    "alphasrc=s={0}x{1}:r={2}:start='{3}'",
 03446                    outWidth.Value,
 03447                    outHeight.Value,
 03448                    framerate ?? 25,
 03449                    reqTicks > 0 ? startTime : 0);
 3450            }
 3451
 03452            return string.Empty;
 3453        }
 3454
 3455        public static string GetSwScaleFilter(
 3456            EncodingJobInfo state,
 3457            EncodingOptions options,
 3458            string videoEncoder,
 3459            int? videoWidth,
 3460            int? videoHeight,
 3461            Video3DFormat? threedFormat,
 3462            int? requestedWidth,
 3463            int? requestedHeight,
 3464            int? requestedMaxWidth,
 3465            int? requestedMaxHeight)
 3466        {
 03467            var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 03468            var isMjpeg = videoEncoder is not null && videoEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase)
 03469            var scaleVal = isV4l2 ? 64 : 2;
 03470            var targetAr = isMjpeg ? "(a*sar)" : "a"; // manually calculate AR when using mjpeg encoder
 3471
 3472            // If fixed dimensions were supplied
 03473            if (requestedWidth.HasValue && requestedHeight.HasValue)
 3474            {
 03475                if (isV4l2)
 3476                {
 03477                    var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 03478                    var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3479
 03480                    return string.Format(
 03481                            CultureInfo.InvariantCulture,
 03482                            "scale=trunc({0}/64)*64:trunc({1}/2)*2",
 03483                            widthParam,
 03484                            heightParam);
 3485                }
 3486
 03487                return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
 3488            }
 3489
 3490            // If Max dimensions were supplied, for width selects lowest even number between input width and width req s
 3491
 03492            if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
 3493            {
 03494                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 03495                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3496
 03497                return string.Format(
 03498                    CultureInfo.InvariantCulture,
 03499                    @"scale=trunc(min(max(iw\,ih*{3})\,min({0}\,{1}*{3}))/{2})*{2}:trunc(min(max(iw/{3}\,ih)\,min({0}/{3
 03500                    maxWidthParam,
 03501                    maxHeightParam,
 03502                    scaleVal,
 03503                    targetAr);
 3504            }
 3505
 3506            // If a fixed width was requested
 03507            if (requestedWidth.HasValue)
 3508            {
 03509                if (threedFormat.HasValue)
 3510                {
 3511                    // This method can handle 0 being passed in for the requested height
 03512                    return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0);
 3513                }
 3514
 03515                var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 3516
 03517                return string.Format(
 03518                    CultureInfo.InvariantCulture,
 03519                    "scale={0}:trunc(ow/{1}/2)*2",
 03520                    widthParam,
 03521                    targetAr);
 3522            }
 3523
 3524            // If a fixed height was requested
 03525            if (requestedHeight.HasValue)
 3526            {
 03527                var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3528
 03529                return string.Format(
 03530                    CultureInfo.InvariantCulture,
 03531                    "scale=trunc(oh*{2}/{1})*{1}:{0}",
 03532                    heightParam,
 03533                    scaleVal,
 03534                    targetAr);
 3535            }
 3536
 3537            // If a max width was requested
 03538            if (requestedMaxWidth.HasValue)
 3539            {
 03540                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 3541
 03542                return string.Format(
 03543                    CultureInfo.InvariantCulture,
 03544                    @"scale=trunc(min(max(iw\,ih*{2})\,{0})/{1})*{1}:trunc(ow/{2}/2)*2",
 03545                    maxWidthParam,
 03546                    scaleVal,
 03547                    targetAr);
 3548            }
 3549
 3550            // If a max height was requested
 03551            if (requestedMaxHeight.HasValue)
 3552            {
 03553                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3554
 03555                return string.Format(
 03556                    CultureInfo.InvariantCulture,
 03557                    @"scale=trunc(oh*{2}/{1})*{1}:min(max(iw/{2}\,ih)\,{0})",
 03558                    maxHeightParam,
 03559                    scaleVal,
 03560                    targetAr);
 3561            }
 3562
 03563            return string.Empty;
 3564        }
 3565
 3566        private static string GetFixedSwScaleFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight
 3567        {
 03568            var widthParam = requestedWidth.ToString(CultureInfo.InvariantCulture);
 03569            var heightParam = requestedHeight.ToString(CultureInfo.InvariantCulture);
 3570
 03571            string filter = null;
 3572
 03573            if (threedFormat.HasValue)
 3574            {
 03575                switch (threedFormat.Value)
 3576                {
 3577                    case Video3DFormat.HalfSideBySide:
 03578                        filter = @"crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(i
 3579                        // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars
 03580                        break;
 3581                    case Video3DFormat.FullSideBySide:
 03582                        filter = @"crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3583                        // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the s
 03584                        break;
 3585                    case Video3DFormat.HalfTopAndBottom:
 03586                        filter = @"crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(
 3587                        // htab crop height in half,scale to correct size, set the display aspect,crop out any black bar
 03588                        break;
 3589                    case Video3DFormat.FullTopAndBottom:
 03590                        filter = @"crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3591                        // ftab crop height in half, set the display aspect,crop out any black bars we may have made the
 3592                        break;
 3593                    default:
 3594                        break;
 3595                }
 3596            }
 3597
 3598            // default
 03599            if (filter is null)
 3600            {
 03601                if (requestedHeight > 0)
 3602                {
 03603                    filter = "scale=trunc({0}/2)*2:trunc({1}/2)*2";
 3604                }
 3605                else
 3606                {
 03607                    filter = "scale={0}:trunc({0}/a/2)*2";
 3608                }
 3609            }
 3610
 03611            return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam);
 3612        }
 3613
 3614        public static string GetSwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options)
 3615        {
 03616            var doubleRateDeint = options.DeinterlaceDoubleRate && state.VideoStream?.ReferenceFrameRate <= 30;
 03617            return string.Format(
 03618                CultureInfo.InvariantCulture,
 03619                "{0}={1}:-1:0",
 03620                options.DeinterlaceMethod.ToString().ToLowerInvariant(),
 03621                doubleRateDeint ? "1" : "0");
 3622        }
 3623
 3624        public string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix)
 3625        {
 03626            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03627            if (hwDeintSuffix.Contains("cuda", StringComparison.OrdinalIgnoreCase))
 3628            {
 03629                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3630
 03631                return string.Format(
 03632                    CultureInfo.InvariantCulture,
 03633                    "{0}_cuda={1}:-1:0",
 03634                    useBwdif ? "bwdif" : "yadif",
 03635                    doubleRateDeint ? "1" : "0");
 3636            }
 3637
 03638            if (hwDeintSuffix.Contains("opencl", StringComparison.OrdinalIgnoreCase))
 3639            {
 03640                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif;
 3641
 03642                if (_mediaEncoder.SupportsFilter("yadif_opencl")
 03643                    && _mediaEncoder.SupportsFilter("bwdif_opencl"))
 3644                {
 03645                    return string.Format(
 03646                        CultureInfo.InvariantCulture,
 03647                        "{0}_opencl={1}:-1:0",
 03648                        useBwdif ? "bwdif" : "yadif",
 03649                        doubleRateDeint ? "1" : "0");
 3650                }
 3651            }
 3652
 03653            if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
 3654            {
 03655                return string.Format(
 03656                    CultureInfo.InvariantCulture,
 03657                    "deinterlace_vaapi=rate={0}",
 03658                    doubleRateDeint ? "field" : "frame");
 3659            }
 3660
 03661            if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
 3662            {
 03663                return "deinterlace_qsv=mode=2";
 3664            }
 3665
 03666            if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
 3667            {
 03668                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3669
 03670                return string.Format(
 03671                    CultureInfo.InvariantCulture,
 03672                    "{0}_videotoolbox={1}:-1:0",
 03673                    useBwdif ? "bwdif" : "yadif",
 03674                    doubleRateDeint ? "1" : "0");
 3675            }
 3676
 03677            return string.Empty;
 3678        }
 3679
 3680        private string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat, bool forc
 3681        {
 03682            if (string.IsNullOrEmpty(hwTonemapSuffix))
 3683            {
 03684                return string.Empty;
 3685            }
 3686
 03687            var args = string.Empty;
 03688            var algorithm = options.TonemappingAlgorithm.ToString().ToLowerInvariant();
 03689            var mode = options.TonemappingMode.ToString().ToLowerInvariant();
 03690            var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 03691            var rangeString = range.ToString().ToLowerInvariant();
 3692
 03693            if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
 3694            {
 03695                var doVaVppProcamp = false;
 03696                var procampParams = string.Empty;
 03697                if (options.VppTonemappingBrightness != 0
 03698                    && options.VppTonemappingBrightness >= -100
 03699                    && options.VppTonemappingBrightness <= 100)
 3700                {
 03701                    procampParams += "procamp_vaapi=b={0}";
 03702                    doVaVppProcamp = true;
 3703                }
 3704
 03705                if (options.VppTonemappingContrast > 1
 03706                    && options.VppTonemappingContrast <= 10)
 3707                {
 03708                    procampParams += doVaVppProcamp ? ":c={1}" : "procamp_vaapi=c={1}";
 03709                    doVaVppProcamp = true;
 3710                }
 3711
 03712                args = procampParams + "{2}tonemap_vaapi=format={3}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
 3713
 03714                return string.Format(
 03715                        CultureInfo.InvariantCulture,
 03716                        args,
 03717                        options.VppTonemappingBrightness,
 03718                        options.VppTonemappingContrast,
 03719                        doVaVppProcamp ? "," : string.Empty,
 03720                        videoFormat ?? "nv12");
 3721            }
 3722            else
 3723            {
 03724                args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
 3725
 03726                var useLegacyTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode
 03727                                           && _legacyTonemapModes.Contains(options.TonemappingMode);
 3728
 03729                var useAdvancedTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegAdvancedTonemapMode
 03730                                              && _advancedTonemapModes.Contains(options.TonemappingMode);
 3731
 03732                if (useLegacyTonemapModes || useAdvancedTonemapModes)
 3733                {
 03734                    args += ":tonemap_mode={5}";
 3735                }
 3736
 03737                if (options.TonemappingParam != 0)
 3738                {
 03739                    args += ":param={6}";
 3740                }
 3741
 03742                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3743                {
 03744                    args += ":range={7}";
 3745                }
 3746            }
 3747
 03748            return string.Format(
 03749                    CultureInfo.InvariantCulture,
 03750                    args,
 03751                    hwTonemapSuffix,
 03752                    videoFormat ?? "nv12",
 03753                    algorithm,
 03754                    options.TonemappingPeak,
 03755                    options.TonemappingDesat,
 03756                    mode,
 03757                    options.TonemappingParam,
 03758                    rangeString);
 3759        }
 3760
 3761        private string GetLibplaceboFilter(
 3762            EncodingOptions options,
 3763            string videoFormat,
 3764            bool doTonemap,
 3765            int? videoWidth,
 3766            int? videoHeight,
 3767            int? requestedWidth,
 3768            int? requestedHeight,
 3769            int? requestedMaxWidth,
 3770            int? requestedMaxHeight,
 3771            bool forceFullRange)
 3772        {
 03773            var (outWidth, outHeight) = GetFixedOutputSize(
 03774                videoWidth,
 03775                videoHeight,
 03776                requestedWidth,
 03777                requestedHeight,
 03778                requestedMaxWidth,
 03779                requestedMaxHeight);
 3780
 03781            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03782            var isSizeFixed = !videoWidth.HasValue
 03783                || outWidth.Value != videoWidth.Value
 03784                || !videoHeight.HasValue
 03785                || outHeight.Value != videoHeight.Value;
 3786
 03787            var sizeArg = isSizeFixed ? (":w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty;
 03788            var formatArg = isFormatFixed ? (":format=" + videoFormat) : string.Empty;
 03789            var tonemapArg = string.Empty;
 3790
 03791            if (doTonemap)
 3792            {
 03793                var algorithm = options.TonemappingAlgorithm;
 03794                var algorithmString = "clip";
 03795                var mode = options.TonemappingMode;
 03796                var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 3797
 03798                if (algorithm == TonemappingAlgorithm.bt2390)
 3799                {
 03800                    algorithmString = "bt.2390";
 3801                }
 03802                else if (algorithm != TonemappingAlgorithm.none)
 3803                {
 03804                    algorithmString = algorithm.ToString().ToLowerInvariant();
 3805                }
 3806
 03807                tonemapArg = $":tonemapping={algorithmString}:peak_detect=0:color_primaries=bt709:color_trc=bt709:colors
 3808
 03809                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3810                {
 03811                    tonemapArg += ":range=" + range.ToString().ToLowerInvariant();
 3812                }
 3813            }
 3814
 03815            return string.Format(
 03816                CultureInfo.InvariantCulture,
 03817                "libplacebo=upscaler=none:downscaler=none{0}{1}{2}",
 03818                sizeArg,
 03819                formatArg,
 03820                tonemapArg);
 3821        }
 3822
 3823        public string GetVideoTransposeDirection(EncodingJobInfo state)
 3824        {
 03825            return (state.VideoStream?.Rotation ?? 0) switch
 03826            {
 03827                90 => "cclock",
 03828                180 => "reversal",
 03829                -90 => "clock",
 03830                -180 => "reversal",
 03831                _ => string.Empty
 03832            };
 3833        }
 3834
 3835        /// <summary>
 3836        /// Gets the parameter of software filter chain.
 3837        /// </summary>
 3838        /// <param name="state">Encoding state.</param>
 3839        /// <param name="options">Encoding options.</param>
 3840        /// <param name="vidEncoder">Video encoder to use.</param>
 3841        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3842        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetSwVidFilterChain(
 3843            EncodingJobInfo state,
 3844            EncodingOptions options,
 3845            string vidEncoder)
 3846        {
 03847            var inW = state.VideoStream?.Width;
 03848            var inH = state.VideoStream?.Height;
 03849            var reqW = state.BaseRequest.Width;
 03850            var reqH = state.BaseRequest.Height;
 03851            var reqMaxW = state.BaseRequest.MaxWidth;
 03852            var reqMaxH = state.BaseRequest.MaxHeight;
 03853            var threeDFormat = state.MediaSource.Video3DFormat;
 3854
 03855            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03856            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03857            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 03858            var isV4l2Encoder = vidEncoder.Contains("h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 3859
 03860            var doDeintH2645 = IsDeinterlaceAvailable(state);
 03861            var doToneMap = IsSwTonemapAvailable(state, options);
 03862            var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI;
 3863
 03864            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03865            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03866            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 3867
 03868            var rotation = state.VideoStream?.Rotation ?? 0;
 03869            var swapWAndH = Math.Abs(rotation) == 90;
 03870            var swpInW = swapWAndH ? inH : inW;
 03871            var swpInH = swapWAndH ? inW : inH;
 3872
 3873            /* Make main filters for video stream */
 03874            var mainFilters = new List<string>();
 3875
 03876            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doToneMap));
 3877
 3878            // INPUT sw surface(memory/copy-back from vram)
 3879            // sw deint
 03880            if (doDeintH2645)
 3881            {
 03882                var deintFilter = GetSwDeinterlaceFilter(state, options);
 03883                mainFilters.Add(deintFilter);
 3884            }
 3885
 03886            var outFormat = isSwDecoder ? "yuv420p" : "nv12";
 03887            var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, r
 03888            if (isVaapiEncoder)
 3889            {
 03890                outFormat = "nv12";
 3891            }
 03892            else if (isV4l2Encoder)
 3893            {
 03894                outFormat = "yuv420p";
 3895            }
 3896
 3897            // sw scale
 03898            mainFilters.Add(swScaleFilter);
 3899
 3900            // sw tonemap
 03901            if (doToneMap)
 3902            {
 3903                // tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary
 03904                var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat;
 03905                var tonemapArgString = "tonemapx=tonemap={0}:desat={1}:peak={2}:t=bt709:m=bt709:p=bt709:format={3}";
 3906
 03907                if (options.TonemappingParam != 0)
 3908                {
 03909                    tonemapArgString += ":param={4}";
 3910                }
 3911
 03912                var range = options.TonemappingRange;
 03913                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3914                {
 03915                    tonemapArgString += ":range={5}";
 3916                }
 3917
 03918                var tonemapArgs = string.Format(
 03919                    CultureInfo.InvariantCulture,
 03920                    tonemapArgString,
 03921                    options.TonemappingAlgorithm,
 03922                    options.TonemappingDesat,
 03923                    options.TonemappingPeak,
 03924                    tonemapFormat,
 03925                    options.TonemappingParam,
 03926                    options.TonemappingRange);
 3927
 03928                mainFilters.Add(tonemapArgs);
 3929            }
 3930            else
 3931            {
 3932                // OUTPUT yuv420p/nv12 surface(memory)
 03933                mainFilters.Add("format=" + outFormat);
 3934            }
 3935
 3936            /* Make sub and overlay filters for subtitle stream */
 03937            var subFilters = new List<string>();
 03938            var overlayFilters = new List<string>();
 03939            if (hasTextSubs)
 3940            {
 3941                // subtitles=f='*.ass':alpha=0
 03942                var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03943                mainFilters.Add(textSubtitlesFilter);
 3944            }
 03945            else if (hasGraphicalSubs)
 3946            {
 03947                var subW = state.SubtitleStream?.Width;
 03948                var subH = state.SubtitleStream?.Height;
 03949                var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW
 03950                subFilters.Add(subPreProcFilters);
 03951                overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 3952            }
 3953
 03954            return (mainFilters, subFilters, overlayFilters);
 3955        }
 3956
 3957        /// <summary>
 3958        /// Gets the parameter of Nvidia NVENC filter chain.
 3959        /// </summary>
 3960        /// <param name="state">Encoding state.</param>
 3961        /// <param name="options">Encoding options.</param>
 3962        /// <param name="vidEncoder">Video encoder to use.</param>
 3963        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3964        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFilterChain(
 3965            EncodingJobInfo state,
 3966            EncodingOptions options,
 3967            string vidEncoder)
 3968        {
 03969            if (options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 3970            {
 03971                return (null, null, null);
 3972            }
 3973
 03974            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03975            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03976            var isSwEncoder = !vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 3977
 3978            // legacy cuvid pipeline(copy-back)
 03979            if ((isSwDecoder && isSwEncoder)
 03980                || !IsCudaFullSupported()
 03981                || !_mediaEncoder.SupportsFilter("alphasrc"))
 3982            {
 03983                return GetSwVidFilterChain(state, options, vidEncoder);
 3984            }
 3985
 3986            // preferred nvdec/cuvid + cuda filters + nvenc pipeline
 03987            return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 3988        }
 3989
 3990        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFiltersPrefe
 3991            EncodingJobInfo state,
 3992            EncodingOptions options,
 3993            string vidDecoder,
 3994            string vidEncoder)
 3995        {
 03996            var inW = state.VideoStream?.Width;
 03997            var inH = state.VideoStream?.Height;
 03998            var reqW = state.BaseRequest.Width;
 03999            var reqH = state.BaseRequest.Height;
 04000            var reqMaxW = state.BaseRequest.MaxWidth;
 04001            var reqMaxH = state.BaseRequest.MaxHeight;
 04002            var threeDFormat = state.MediaSource.Video3DFormat;
 4003
 04004            var isNvDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 04005            var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 04006            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04007            var isSwEncoder = !isNvencEncoder;
 04008            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04009            var isCuInCuOut = isNvDecoder && isNvencEncoder;
 4010
 04011            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 04012            var doDeintH2645 = IsDeinterlaceAvailable(state);
 04013            var doCuTonemap = IsHwTonemapAvailable(state, options);
 4014
 04015            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04016            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04017            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04018            var hasAssSubs = hasSubs
 04019                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04020                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04021            var subW = state.SubtitleStream?.Width;
 04022            var subH = state.SubtitleStream?.Height;
 4023
 04024            var rotation = state.VideoStream?.Rotation ?? 0;
 04025            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04026            var doCuTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_cuda");
 04027            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isNvDecoder && doCuTranspose));
 04028            var swpInW = swapWAndH ? inH : inW;
 04029            var swpInH = swapWAndH ? inW : inH;
 4030
 4031            /* Make main filters for video stream */
 04032            var mainFilters = new List<string>();
 4033
 04034            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doCuTonemap));
 4035
 04036            if (isSwDecoder)
 4037            {
 4038                // INPUT sw surface(memory)
 4039                // sw deint
 04040                if (doDeintH2645)
 4041                {
 04042                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04043                    mainFilters.Add(swDeintFilter);
 4044                }
 4045
 04046                var outFormat = doCuTonemap ? "yuv420p10le" : "yuv420p";
 04047                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 4048                // sw scale
 04049                mainFilters.Add(swScaleFilter);
 04050                mainFilters.Add($"format={outFormat}");
 4051
 4052                // sw => hw
 04053                if (doCuTonemap)
 4054                {
 04055                    mainFilters.Add("hwupload=derive_device=cuda");
 4056                }
 4057            }
 4058
 04059            if (isNvDecoder)
 4060            {
 4061                // INPUT cuda surface(vram)
 4062                // hw deint
 04063                if (doDeintH2645)
 4064                {
 04065                    var deintFilter = GetHwDeinterlaceFilter(state, options, "cuda");
 04066                    mainFilters.Add(deintFilter);
 4067                }
 4068
 4069                // hw transpose
 04070                if (doCuTranspose)
 4071                {
 04072                    mainFilters.Add($"transpose_cuda=dir={transposeDir}");
 4073                }
 4074
 04075                var isRext = IsVideoStreamHevcRext(state);
 04076                var outFormat = doCuTonemap ? (isRext ? "p010" : string.Empty) : "yuv420p";
 04077                var hwScaleFilter = GetHwScaleFilter("scale", "cuda", outFormat, false, swpInW, swpInH, reqW, reqH, reqM
 4078                // hw scale
 04079                mainFilters.Add(hwScaleFilter);
 4080            }
 4081
 4082            // hw tonemap
 04083            if (doCuTonemap)
 4084            {
 04085                var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p", isMjpegEncoder);
 04086                mainFilters.Add(tonemapFilter);
 4087            }
 4088
 04089            var memoryOutput = false;
 04090            var isUploadForCuTonemap = isSwDecoder && doCuTonemap;
 04091            if ((isNvDecoder && isSwEncoder) || (isUploadForCuTonemap && hasSubs))
 4092            {
 04093                memoryOutput = true;
 4094
 4095                // OUTPUT yuv420p surface(memory)
 04096                mainFilters.Add("hwdownload");
 04097                mainFilters.Add("format=yuv420p");
 4098            }
 4099
 4100            // OUTPUT yuv420p surface(memory)
 04101            if (isSwDecoder && isNvencEncoder && !isUploadForCuTonemap)
 4102            {
 04103                memoryOutput = true;
 4104            }
 4105
 04106            if (memoryOutput)
 4107            {
 4108                // text subtitles
 04109                if (hasTextSubs)
 4110                {
 04111                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04112                    mainFilters.Add(textSubtitlesFilter);
 4113                }
 4114            }
 4115
 4116            // OUTPUT cuda(yuv420p) surface(vram)
 4117
 4118            /* Make sub and overlay filters for subtitle stream */
 04119            var subFilters = new List<string>();
 04120            var overlayFilters = new List<string>();
 04121            if (isCuInCuOut)
 4122            {
 04123                if (hasSubs)
 4124                {
 04125                    var alphaFormatOpt = string.Empty;
 04126                    if (hasGraphicalSubs)
 4127                    {
 04128                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04129                        subFilters.Add(subPreProcFilters);
 04130                        subFilters.Add("format=yuva420p");
 4131                    }
 04132                    else if (hasTextSubs)
 4133                    {
 04134                        var framerate = state.VideoStream?.RealFrameRate;
 04135                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4136
 4137                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04138                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04139                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04140                        subFilters.Add(alphaSrcFilter);
 04141                        subFilters.Add("format=yuva420p");
 04142                        subFilters.Add(subTextSubtitlesFilter);
 4143
 04144                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayCudaAlphaFormat)
 04145                            ? ":alpha_format=premultiplied" : string.Empty;
 4146                    }
 4147
 04148                    subFilters.Add("hwupload=derive_device=cuda");
 04149                    overlayFilters.Add($"overlay_cuda=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 4150                }
 4151            }
 4152            else
 4153            {
 04154                if (hasGraphicalSubs)
 4155                {
 04156                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04157                    subFilters.Add(subPreProcFilters);
 04158                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4159                }
 4160            }
 4161
 04162            return (mainFilters, subFilters, overlayFilters);
 4163        }
 4164
 4165        /// <summary>
 4166        /// Gets the parameter of AMD AMF filter chain.
 4167        /// </summary>
 4168        /// <param name="state">Encoding state.</param>
 4169        /// <param name="options">Encoding options.</param>
 4170        /// <param name="vidEncoder">Video encoder to use.</param>
 4171        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4172        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVidFilterChain(
 4173            EncodingJobInfo state,
 4174            EncodingOptions options,
 4175            string vidEncoder)
 4176        {
 04177            if (options.HardwareAccelerationType != HardwareAccelerationType.amf)
 4178            {
 04179                return (null, null, null);
 4180            }
 4181
 04182            var isWindows = OperatingSystem.IsWindows();
 04183            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04184            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04185            var isSwEncoder = !vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04186            var isAmfDx11OclSupported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va") && IsOpenclFullSupported()
 4187
 4188            // legacy d3d11va pipeline(copy-back)
 04189            if ((isSwDecoder && isSwEncoder)
 04190                || !isAmfDx11OclSupported
 04191                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4192            {
 04193                return GetSwVidFilterChain(state, options, vidEncoder);
 4194            }
 4195
 4196            // preferred d3d11va + opencl filters + amf pipeline
 04197            return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4198        }
 4199
 4200        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdDx11VidFiltersPref
 4201            EncodingJobInfo state,
 4202            EncodingOptions options,
 4203            string vidDecoder,
 4204            string vidEncoder)
 4205        {
 04206            var inW = state.VideoStream?.Width;
 04207            var inH = state.VideoStream?.Height;
 04208            var reqW = state.BaseRequest.Width;
 04209            var reqH = state.BaseRequest.Height;
 04210            var reqMaxW = state.BaseRequest.MaxWidth;
 04211            var reqMaxH = state.BaseRequest.MaxHeight;
 04212            var threeDFormat = state.MediaSource.Video3DFormat;
 4213
 04214            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04215            var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04216            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04217            var isSwEncoder = !isAmfEncoder;
 04218            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04219            var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder;
 4220
 04221            var doDeintH2645 = IsDeinterlaceAvailable(state);
 04222            var doOclTonemap = IsHwTonemapAvailable(state, options);
 4223
 04224            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04225            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04226            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04227            var hasAssSubs = hasSubs
 04228                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04229                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04230            var subW = state.SubtitleStream?.Width;
 04231            var subH = state.SubtitleStream?.Height;
 4232
 04233            var rotation = state.VideoStream?.Rotation ?? 0;
 04234            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04235            var doOclTranspose = !string.IsNullOrEmpty(transposeDir)
 04236                && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TransposeOpenclReversal);
 04237            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isD3d11vaDecoder && doOclTranspose));
 04238            var swpInW = swapWAndH ? inH : inW;
 04239            var swpInH = swapWAndH ? inW : inH;
 4240
 4241            /* Make main filters for video stream */
 04242            var mainFilters = new List<string>();
 4243
 04244            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 4245
 04246            if (isSwDecoder)
 4247            {
 4248                // INPUT sw surface(memory)
 4249                // sw deint
 04250                if (doDeintH2645)
 4251                {
 04252                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04253                    mainFilters.Add(swDeintFilter);
 4254                }
 4255
 04256                var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
 04257                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 4258                // sw scale
 04259                mainFilters.Add(swScaleFilter);
 04260                mainFilters.Add($"format={outFormat}");
 4261
 4262                // keep video at memory except ocl tonemap,
 4263                // since the overhead caused by hwupload >>> using sw filter.
 4264                // sw => hw
 04265                if (doOclTonemap)
 4266                {
 04267                    mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=24");
 04268                    mainFilters.Add("format=d3d11");
 04269                    mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4270                }
 4271            }
 4272
 04273            if (isD3d11vaDecoder)
 4274            {
 4275                // INPUT d3d11 surface(vram)
 4276                // map from d3d11va to opencl via d3d11-opencl interop.
 04277                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4278
 4279                // hw deint
 04280                if (doDeintH2645)
 4281                {
 04282                    var deintFilter = GetHwDeinterlaceFilter(state, options, "opencl");
 04283                    mainFilters.Add(deintFilter);
 4284                }
 4285
 4286                // hw transpose
 04287                if (doOclTranspose)
 4288                {
 04289                    mainFilters.Add($"transpose_opencl=dir={transposeDir}");
 4290                }
 4291
 04292                var outFormat = doOclTonemap ? string.Empty : "nv12";
 04293                var hwScaleFilter = GetHwScaleFilter("scale", "opencl", outFormat, false, swpInW, swpInH, reqW, reqH, re
 4294                // hw scale
 04295                mainFilters.Add(hwScaleFilter);
 4296            }
 4297
 4298            // hw tonemap
 04299            if (doOclTonemap)
 4300            {
 04301                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04302                mainFilters.Add(tonemapFilter);
 4303            }
 4304
 04305            var memoryOutput = false;
 04306            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04307            if (isD3d11vaDecoder && isSwEncoder)
 4308            {
 04309                memoryOutput = true;
 4310
 4311                // OUTPUT nv12 surface(memory)
 4312                // prefer hwmap to hwdownload on opencl.
 04313                var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap=mode=read";
 04314                mainFilters.Add(hwTransferFilter);
 04315                mainFilters.Add("format=nv12");
 4316            }
 4317
 4318            // OUTPUT yuv420p surface
 04319            if (isSwDecoder && isAmfEncoder && !isUploadForOclTonemap)
 4320            {
 04321                memoryOutput = true;
 4322            }
 4323
 04324            if (memoryOutput)
 4325            {
 4326                // text subtitles
 04327                if (hasTextSubs)
 4328                {
 04329                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04330                    mainFilters.Add(textSubtitlesFilter);
 4331                }
 4332            }
 4333
 04334            if ((isDxInDxOut || isUploadForOclTonemap) && !hasSubs)
 4335            {
 4336                // OUTPUT d3d11(nv12) surface(vram)
 4337                // reverse-mapping via d3d11-opencl interop.
 04338                mainFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04339                mainFilters.Add("format=d3d11");
 4340            }
 4341
 4342            /* Make sub and overlay filters for subtitle stream */
 04343            var subFilters = new List<string>();
 04344            var overlayFilters = new List<string>();
 04345            if (isDxInDxOut || isUploadForOclTonemap)
 4346            {
 04347                if (hasSubs)
 4348                {
 04349                    var alphaFormatOpt = string.Empty;
 04350                    if (hasGraphicalSubs)
 4351                    {
 04352                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04353                        subFilters.Add(subPreProcFilters);
 04354                        subFilters.Add("format=yuva420p");
 4355                    }
 04356                    else if (hasTextSubs)
 4357                    {
 04358                        var framerate = state.VideoStream?.RealFrameRate;
 04359                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4360
 4361                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04362                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04363                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04364                        subFilters.Add(alphaSrcFilter);
 04365                        subFilters.Add("format=yuva420p");
 04366                        subFilters.Add(subTextSubtitlesFilter);
 4367
 04368                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclAlphaForma
 04369                            ? ":alpha_format=premultiplied" : string.Empty;
 4370                    }
 4371
 04372                    subFilters.Add("hwupload=derive_device=opencl");
 04373                    overlayFilters.Add($"overlay_opencl=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 04374                    overlayFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04375                    overlayFilters.Add("format=d3d11");
 4376                }
 4377            }
 04378            else if (memoryOutput)
 4379            {
 04380                if (hasGraphicalSubs)
 4381                {
 04382                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04383                    subFilters.Add(subPreProcFilters);
 04384                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4385                }
 4386            }
 4387
 04388            return (mainFilters, subFilters, overlayFilters);
 4389        }
 4390
 4391        /// <summary>
 4392        /// Gets the parameter of Intel QSV filter chain.
 4393        /// </summary>
 4394        /// <param name="state">Encoding state.</param>
 4395        /// <param name="options">Encoding options.</param>
 4396        /// <param name="vidEncoder">Video encoder to use.</param>
 4397        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4398        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVidFilterChain(
 4399            EncodingJobInfo state,
 4400            EncodingOptions options,
 4401            string vidEncoder)
 4402        {
 04403            if (options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 4404            {
 04405                return (null, null, null);
 4406            }
 4407
 04408            var isWindows = OperatingSystem.IsWindows();
 04409            var isLinux = OperatingSystem.IsLinux();
 04410            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04411            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04412            var isSwEncoder = !vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04413            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 04414            var isIntelDx11OclSupported = isWindows
 04415                && _mediaEncoder.SupportsHwaccel("d3d11va")
 04416                && isQsvOclSupported;
 04417            var isIntelVaapiOclSupported = isLinux
 04418                && IsVaapiSupported(state)
 04419                && isQsvOclSupported;
 4420
 4421            // legacy qsv pipeline(copy-back)
 04422            if ((isSwDecoder && isSwEncoder)
 04423                || (!isIntelVaapiOclSupported && !isIntelDx11OclSupported)
 04424                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4425            {
 04426                return GetSwVidFilterChain(state, options, vidEncoder);
 4427            }
 4428
 4429            // preferred qsv(vaapi) + opencl filters pipeline
 04430            if (isIntelVaapiOclSupported)
 4431            {
 04432                return GetIntelQsvVaapiVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4433            }
 4434
 4435            // preferred qsv(d3d11) + opencl filters pipeline
 04436            if (isIntelDx11OclSupported)
 4437            {
 04438                return GetIntelQsvDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4439            }
 4440
 04441            return (null, null, null);
 4442        }
 4443
 4444        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvDx11VidFilter
 4445            EncodingJobInfo state,
 4446            EncodingOptions options,
 4447            string vidDecoder,
 4448            string vidEncoder)
 4449        {
 04450            var inW = state.VideoStream?.Width;
 04451            var inH = state.VideoStream?.Height;
 04452            var reqW = state.BaseRequest.Width;
 04453            var reqH = state.BaseRequest.Height;
 04454            var reqMaxW = state.BaseRequest.MaxWidth;
 04455            var reqMaxH = state.BaseRequest.MaxHeight;
 04456            var threeDFormat = state.MediaSource.Video3DFormat;
 4457
 04458            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04459            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04460            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04461            var isHwDecoder = isD3d11vaDecoder || isQsvDecoder;
 04462            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04463            var isSwEncoder = !isQsvEncoder;
 04464            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04465            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4466
 04467            var doDeintH2645 = IsDeinterlaceAvailable(state);
 04468            var doVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04469            var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options);
 04470            var doTonemap = doVppTonemap || doOclTonemap;
 4471
 04472            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04473            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04474            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04475            var hasAssSubs = hasSubs
 04476                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04477                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04478            var subW = state.SubtitleStream?.Width;
 04479            var subH = state.SubtitleStream?.Height;
 4480
 04481            var rotation = state.VideoStream?.Rotation ?? 0;
 04482            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04483            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04484            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isD3d11vaDecoder || isQsvDecoder) && doVppTran
 04485            var swpInW = swapWAndH ? inH : inW;
 04486            var swpInH = swapWAndH ? inW : inH;
 4487
 4488            /* Make main filters for video stream */
 04489            var mainFilters = new List<string>();
 4490
 04491            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4492
 04493            if (isSwDecoder)
 4494            {
 4495                // INPUT sw surface(memory)
 4496                // sw deint
 04497                if (doDeintH2645)
 4498                {
 04499                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04500                    mainFilters.Add(swDeintFilter);
 4501                }
 4502
 04503                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04504                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04505                if (isMjpegEncoder && !doOclTonemap)
 4506                {
 4507                    // sw decoder + hw mjpeg encoder
 04508                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4509                }
 4510
 4511                // sw scale
 04512                mainFilters.Add(swScaleFilter);
 04513                mainFilters.Add($"format={outFormat}");
 4514
 4515                // keep video at memory except ocl tonemap,
 4516                // since the overhead caused by hwupload >>> using sw filter.
 4517                // sw => hw
 04518                if (doOclTonemap)
 4519                {
 04520                    mainFilters.Add("hwupload=derive_device=opencl");
 4521                }
 4522            }
 04523            else if (isD3d11vaDecoder || isQsvDecoder)
 4524            {
 04525                var isRext = IsVideoStreamHevcRext(state);
 04526                var twoPassVppTonemap = false;
 04527                var doVppFullRangeOut = isMjpegEncoder
 04528                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04529                var doVppScaleModeHq = isMjpegEncoder
 04530                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 04531                var doVppProcamp = false;
 04532                var procampParams = string.Empty;
 04533                var procampParamsString = string.Empty;
 04534                if (doVppTonemap)
 4535                {
 04536                    if (isRext)
 4537                    {
 4538                        // VPP tonemap requires p010 input
 04539                        twoPassVppTonemap = true;
 4540                    }
 4541
 04542                    if (options.VppTonemappingBrightness != 0
 04543                        && options.VppTonemappingBrightness >= -100
 04544                        && options.VppTonemappingBrightness <= 100)
 4545                    {
 04546                        procampParamsString += ":brightness={0}";
 04547                        twoPassVppTonemap = doVppProcamp = true;
 4548                    }
 4549
 04550                    if (options.VppTonemappingContrast > 1
 04551                        && options.VppTonemappingContrast <= 10)
 4552                    {
 04553                        procampParamsString += ":contrast={1}";
 04554                        twoPassVppTonemap = doVppProcamp = true;
 4555                    }
 4556
 04557                    if (doVppProcamp)
 4558                    {
 04559                        procampParamsString += ":procamp=1:async_depth=2";
 04560                        procampParams = string.Format(
 04561                            CultureInfo.InvariantCulture,
 04562                            procampParamsString,
 04563                            options.VppTonemappingBrightness,
 04564                            options.VppTonemappingContrast);
 4565                    }
 4566                }
 4567
 04568                var outFormat = doOclTonemap ? ((doVppTranspose || isRext) ? "p010" : string.Empty) : "nv12";
 04569                outFormat = twoPassVppTonemap ? "p010" : outFormat;
 4570
 04571                var swapOutputWandH = doVppTranspose && swapWAndH;
 04572                var hwScaleFilter = GetHwScaleFilter("vpp", "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, req
 4573
 4574                // d3d11va doesn't support dynamic pool size, use vpp filter ctx to relay
 4575                // to prevent encoder async and bframes from exhausting the decoder pool.
 04576                if (!string.IsNullOrEmpty(hwScaleFilter) && isD3d11vaDecoder)
 4577                {
 04578                    hwScaleFilter += ":passthrough=0";
 4579                }
 4580
 04581                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose)
 4582                {
 04583                    hwScaleFilter += $":transpose={transposeDir}";
 4584                }
 4585
 04586                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4587                {
 04588                    hwScaleFilter += (doVppFullRangeOut && !doOclTonemap) ? ":out_range=pc" : string.Empty;
 04589                    hwScaleFilter += doVppScaleModeHq ? ":scale_mode=hq" : string.Empty;
 4590                }
 4591
 04592                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTonemap)
 4593                {
 04594                    hwScaleFilter += doVppProcamp ? procampParams : (twoPassVppTonemap ? string.Empty : ":tonemap=1");
 4595                }
 4596
 04597                if (isD3d11vaDecoder)
 4598                {
 04599                    if (!string.IsNullOrEmpty(hwScaleFilter) || doDeintH2645)
 4600                    {
 4601                        // INPUT d3d11 surface(vram)
 4602                        // map from d3d11va to qsv.
 04603                        mainFilters.Add("hwmap=derive_device=qsv");
 4604                    }
 4605                }
 4606
 4607                // hw deint
 04608                if (doDeintH2645)
 4609                {
 04610                    var deintFilter = GetHwDeinterlaceFilter(state, options, "qsv");
 04611                    mainFilters.Add(deintFilter);
 4612                }
 4613
 4614                // hw transpose & scale & tonemap(w/o procamp)
 04615                mainFilters.Add(hwScaleFilter);
 4616
 4617                // hw tonemap(w/ procamp)
 04618                if (doVppTonemap && twoPassVppTonemap)
 4619                {
 04620                    mainFilters.Add("vpp_qsv=tonemap=1:format=nv12:async_depth=2");
 4621                }
 4622
 4623                // force bt709 just in case vpp tonemap is not triggered or using MSDK instead of VPL.
 04624                if (doVppTonemap)
 4625                {
 04626                    mainFilters.Add(GetOverwriteColorPropertiesParam(state, false));
 4627                }
 4628            }
 4629
 04630            if (doOclTonemap && isHwDecoder)
 4631            {
 4632                // map from qsv to opencl via qsv(d3d11)-opencl interop.
 04633                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4634            }
 4635
 4636            // hw tonemap
 04637            if (doOclTonemap)
 4638            {
 04639                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04640                mainFilters.Add(tonemapFilter);
 4641            }
 4642
 04643            var memoryOutput = false;
 04644            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04645            var isHwmapUsable = isSwEncoder && doOclTonemap;
 04646            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4647            {
 04648                memoryOutput = true;
 4649
 4650                // OUTPUT nv12 surface(memory)
 4651                // prefer hwmap to hwdownload on opencl.
 4652                // qsv hwmap is not fully implemented for the time being.
 04653                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04654                mainFilters.Add("format=nv12");
 4655            }
 4656
 4657            // OUTPUT nv12 surface(memory)
 04658            if (isSwDecoder && isQsvEncoder)
 4659            {
 04660                memoryOutput = true;
 4661            }
 4662
 04663            if (memoryOutput)
 4664            {
 4665                // text subtitles
 04666                if (hasTextSubs)
 4667                {
 04668                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04669                    mainFilters.Add(textSubtitlesFilter);
 4670                }
 4671            }
 4672
 04673            if (isQsvInQsvOut && doOclTonemap)
 4674            {
 4675                // OUTPUT qsv(nv12) surface(vram)
 4676                // reverse-mapping via qsv(d3d11)-opencl interop.
 04677                mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1");
 04678                mainFilters.Add("format=qsv");
 4679            }
 4680
 4681            /* Make sub and overlay filters for subtitle stream */
 04682            var subFilters = new List<string>();
 04683            var overlayFilters = new List<string>();
 04684            if (isQsvInQsvOut)
 4685            {
 04686                if (hasSubs)
 4687                {
 04688                    if (hasGraphicalSubs)
 4689                    {
 4690                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04691                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04692                        subFilters.Add(subPreProcFilters);
 04693                        subFilters.Add("format=bgra");
 4694                    }
 04695                    else if (hasTextSubs)
 4696                    {
 04697                        var framerate = state.VideoStream?.RealFrameRate;
 04698                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4699
 4700                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 04701                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04702                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04703                        subFilters.Add(alphaSrcFilter);
 04704                        subFilters.Add("format=bgra");
 04705                        subFilters.Add(subTextSubtitlesFilter);
 4706                    }
 4707
 4708                    // qsv requires a fixed pool size.
 4709                    // default to 64 otherwise it will fail on certain iGPU.
 04710                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4711
 04712                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04713                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04714                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04715                        : string.Empty;
 04716                    var overlayQsvFilter = string.Format(
 04717                        CultureInfo.InvariantCulture,
 04718                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04719                        overlaySize);
 04720                    overlayFilters.Add(overlayQsvFilter);
 4721                }
 4722            }
 04723            else if (memoryOutput)
 4724            {
 04725                if (hasGraphicalSubs)
 4726                {
 04727                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04728                    subFilters.Add(subPreProcFilters);
 04729                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4730                }
 4731            }
 4732
 04733            return (mainFilters, subFilters, overlayFilters);
 4734        }
 4735
 4736        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvVaapiVidFilte
 4737            EncodingJobInfo state,
 4738            EncodingOptions options,
 4739            string vidDecoder,
 4740            string vidEncoder)
 4741        {
 04742            var inW = state.VideoStream?.Width;
 04743            var inH = state.VideoStream?.Height;
 04744            var reqW = state.BaseRequest.Width;
 04745            var reqH = state.BaseRequest.Height;
 04746            var reqMaxW = state.BaseRequest.MaxWidth;
 04747            var reqMaxH = state.BaseRequest.MaxHeight;
 04748            var threeDFormat = state.MediaSource.Video3DFormat;
 4749
 04750            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04751            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04752            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04753            var isHwDecoder = isVaapiDecoder || isQsvDecoder;
 04754            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04755            var isSwEncoder = !isQsvEncoder;
 04756            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04757            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4758
 04759            var doVaVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04760            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04761            var doTonemap = doVaVppTonemap || doOclTonemap;
 04762            var doDeintH2645 = IsDeinterlaceAvailable(state);
 4763
 04764            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04765            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04766            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04767            var hasAssSubs = hasSubs
 04768                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04769                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04770            var subW = state.SubtitleStream?.Width;
 04771            var subH = state.SubtitleStream?.Height;
 4772
 04773            var rotation = state.VideoStream?.Rotation ?? 0;
 04774            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04775            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04776            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isVaapiDecoder || isQsvDecoder) && doVppTransp
 04777            var swpInW = swapWAndH ? inH : inW;
 04778            var swpInH = swapWAndH ? inW : inH;
 4779
 4780            /* Make main filters for video stream */
 04781            var mainFilters = new List<string>();
 4782
 04783            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4784
 04785            if (isSwDecoder)
 4786            {
 4787                // INPUT sw surface(memory)
 4788                // sw deint
 04789                if (doDeintH2645)
 4790                {
 04791                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04792                    mainFilters.Add(swDeintFilter);
 4793                }
 4794
 04795                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04796                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04797                if (isMjpegEncoder && !doOclTonemap)
 4798                {
 4799                    // sw decoder + hw mjpeg encoder
 04800                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4801                }
 4802
 4803                // sw scale
 04804                mainFilters.Add(swScaleFilter);
 04805                mainFilters.Add($"format={outFormat}");
 4806
 4807                // keep video at memory except ocl tonemap,
 4808                // since the overhead caused by hwupload >>> using sw filter.
 4809                // sw => hw
 04810                if (doOclTonemap)
 4811                {
 04812                    mainFilters.Add("hwupload=derive_device=opencl");
 4813                }
 4814            }
 04815            else if (isVaapiDecoder || isQsvDecoder)
 4816            {
 04817                var hwFilterSuffix = isVaapiDecoder ? "vaapi" : "qsv";
 04818                var isRext = IsVideoStreamHevcRext(state);
 04819                var doVppFullRangeOut = isMjpegEncoder
 04820                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04821                var doVppScaleModeHq = isMjpegEncoder
 04822                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 4823
 4824                // INPUT vaapi/qsv surface(vram)
 4825                // hw deint
 04826                if (doDeintH2645)
 4827                {
 04828                    var deintFilter = GetHwDeinterlaceFilter(state, options, hwFilterSuffix);
 04829                    mainFilters.Add(deintFilter);
 4830                }
 4831
 4832                // hw transpose(vaapi vpp)
 04833                if (isVaapiDecoder && doVppTranspose)
 4834                {
 04835                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 4836                }
 4837
 04838                var outFormat = doTonemap ? (((isQsvDecoder && doVppTranspose) || isRext) ? "p010" : string.Empty) : "nv
 04839                var swapOutputWandH = isQsvDecoder && doVppTranspose && swapWAndH;
 04840                var hwScalePrefix = isQsvDecoder ? "vpp" : "scale";
 04841                var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, hwFilterSuffix, outFormat, swapOutputWandH, swpInW, 
 4842
 04843                if (!string.IsNullOrEmpty(hwScaleFilter) && isQsvDecoder && doVppTranspose)
 4844                {
 04845                    hwScaleFilter += $":transpose={transposeDir}";
 4846                }
 4847
 04848                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4849                {
 04850                    hwScaleFilter += ((isQsvDecoder && !doVppFullRangeOut) || doOclTonemap) ? string.Empty : ":out_range
 04851                    hwScaleFilter += isQsvDecoder ? (doVppScaleModeHq ? ":scale_mode=hq" : string.Empty) : ":mode=hq";
 4852                }
 4853
 4854                // allocate extra pool sizes for vaapi vpp scale
 04855                if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder)
 4856                {
 04857                    hwScaleFilter += ":extra_hw_frames=24";
 4858                }
 4859
 4860                // hw transpose(qsv vpp) & scale
 04861                mainFilters.Add(hwScaleFilter);
 4862            }
 4863
 4864            // vaapi vpp tonemap
 04865            if (doVaVppTonemap && isHwDecoder)
 4866            {
 04867                if (isQsvDecoder)
 4868                {
 4869                    // map from qsv to vaapi.
 04870                    mainFilters.Add("hwmap=derive_device=vaapi");
 04871                    mainFilters.Add("format=vaapi");
 4872                }
 4873
 04874                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 04875                mainFilters.Add(tonemapFilter);
 4876
 04877                if (isQsvDecoder)
 4878                {
 4879                    // map from vaapi to qsv.
 04880                    mainFilters.Add("hwmap=derive_device=qsv");
 04881                    mainFilters.Add("format=qsv");
 4882                }
 4883            }
 4884
 04885            if (doOclTonemap && isHwDecoder)
 4886            {
 4887                // map from qsv to opencl via qsv(vaapi)-opencl interop.
 04888                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4889            }
 4890
 4891            // ocl tonemap
 04892            if (doOclTonemap)
 4893            {
 04894                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04895                mainFilters.Add(tonemapFilter);
 4896            }
 4897
 04898            var memoryOutput = false;
 04899            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04900            var isHwmapUsable = isSwEncoder && (doOclTonemap || isVaapiDecoder);
 04901            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4902            {
 04903                memoryOutput = true;
 4904
 4905                // OUTPUT nv12 surface(memory)
 4906                // prefer hwmap to hwdownload on opencl/vaapi.
 4907                // qsv hwmap is not fully implemented for the time being.
 04908                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04909                mainFilters.Add("format=nv12");
 4910            }
 4911
 4912            // OUTPUT nv12 surface(memory)
 04913            if (isSwDecoder && isQsvEncoder)
 4914            {
 04915                memoryOutput = true;
 4916            }
 4917
 04918            if (memoryOutput)
 4919            {
 4920                // text subtitles
 04921                if (hasTextSubs)
 4922                {
 04923                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04924                    mainFilters.Add(textSubtitlesFilter);
 4925                }
 4926            }
 4927
 04928            if (isQsvInQsvOut)
 4929            {
 04930                if (doOclTonemap)
 4931                {
 4932                    // OUTPUT qsv(nv12) surface(vram)
 4933                    // reverse-mapping via qsv(vaapi)-opencl interop.
 4934                    // add extra pool size to avoid the 'cannot allocate memory' error on hevc_qsv.
 04935                    mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1:extra_hw_frames=16");
 04936                    mainFilters.Add("format=qsv");
 4937                }
 04938                else if (isVaapiDecoder)
 4939                {
 04940                    mainFilters.Add("hwmap=derive_device=qsv");
 04941                    mainFilters.Add("format=qsv");
 4942                }
 4943            }
 4944
 4945            /* Make sub and overlay filters for subtitle stream */
 04946            var subFilters = new List<string>();
 04947            var overlayFilters = new List<string>();
 04948            if (isQsvInQsvOut)
 4949            {
 04950                if (hasSubs)
 4951                {
 04952                    if (hasGraphicalSubs)
 4953                    {
 4954                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04955                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04956                        subFilters.Add(subPreProcFilters);
 04957                        subFilters.Add("format=bgra");
 4958                    }
 04959                    else if (hasTextSubs)
 4960                    {
 04961                        var framerate = state.VideoStream?.RealFrameRate;
 04962                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4963
 04964                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04965                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04966                        subFilters.Add(alphaSrcFilter);
 04967                        subFilters.Add("format=bgra");
 04968                        subFilters.Add(subTextSubtitlesFilter);
 4969                    }
 4970
 4971                    // qsv requires a fixed pool size.
 4972                    // default to 64 otherwise it will fail on certain iGPU.
 04973                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4974
 04975                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04976                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04977                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04978                        : string.Empty;
 04979                    var overlayQsvFilter = string.Format(
 04980                        CultureInfo.InvariantCulture,
 04981                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04982                        overlaySize);
 04983                    overlayFilters.Add(overlayQsvFilter);
 4984                }
 4985            }
 04986            else if (memoryOutput)
 4987            {
 04988                if (hasGraphicalSubs)
 4989                {
 04990                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04991                    subFilters.Add(subPreProcFilters);
 04992                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4993                }
 4994            }
 4995
 04996            return (mainFilters, subFilters, overlayFilters);
 4997        }
 4998
 4999        /// <summary>
 5000        /// Gets the parameter of Intel/AMD VAAPI filter chain.
 5001        /// </summary>
 5002        /// <param name="state">Encoding state.</param>
 5003        /// <param name="options">Encoding options.</param>
 5004        /// <param name="vidEncoder">Video encoder to use.</param>
 5005        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5006        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiVidFilterChain(
 5007            EncodingJobInfo state,
 5008            EncodingOptions options,
 5009            string vidEncoder)
 5010        {
 05011            if (options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 5012            {
 05013                return (null, null, null);
 5014            }
 5015
 05016            var isLinux = OperatingSystem.IsLinux();
 05017            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05018            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05019            var isSwEncoder = !vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05020            var isVaapiFullSupported = isLinux && IsVaapiSupported(state) && IsVaapiFullSupported();
 05021            var isVaapiOclSupported = isVaapiFullSupported && IsOpenclFullSupported();
 05022            var isVaapiVkSupported = isVaapiFullSupported && IsVulkanFullSupported();
 5023
 5024            // legacy vaapi pipeline(copy-back)
 05025            if ((isSwDecoder && isSwEncoder)
 05026                || !isVaapiOclSupported
 05027                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5028            {
 05029                var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
 5030
 05031                if (!isSwEncoder)
 5032                {
 05033                    var newfilters = new List<string>();
 05034                    var noOverlay = swFilterChain.OverlayFilters.Count == 0;
 05035                    newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
 05036                    newfilters.Add("hwupload=derive_device=vaapi");
 5037
 05038                    var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
 05039                    var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
 05040                    return (mainFilters, swFilterChain.SubFilters, overlayFilters);
 5041                }
 5042
 05043                return swFilterChain;
 5044            }
 5045
 5046            // preferred vaapi + opencl filters pipeline
 05047            if (_mediaEncoder.IsVaapiDeviceInteliHD)
 5048            {
 5049                // Intel iHD path, with extra vpp tonemap and overlay support.
 05050                return GetIntelVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5051            }
 5052
 5053            // preferred vaapi + vulkan filters pipeline
 05054            if (_mediaEncoder.IsVaapiDeviceAmd
 05055                && isVaapiVkSupported
 05056                && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 05057                && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 5058            {
 5059                // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support.
 05060                return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5061            }
 5062
 5063            // Intel i965 and Amd legacy driver path, only featuring scale and deinterlace support.
 05064            return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5065        }
 5066
 5067        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVaapiFullVidFilt
 5068            EncodingJobInfo state,
 5069            EncodingOptions options,
 5070            string vidDecoder,
 5071            string vidEncoder)
 5072        {
 05073            var inW = state.VideoStream?.Width;
 05074            var inH = state.VideoStream?.Height;
 05075            var reqW = state.BaseRequest.Width;
 05076            var reqH = state.BaseRequest.Height;
 05077            var reqMaxW = state.BaseRequest.MaxWidth;
 05078            var reqMaxH = state.BaseRequest.MaxHeight;
 05079            var threeDFormat = state.MediaSource.Video3DFormat;
 5080
 05081            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05082            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05083            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05084            var isSwEncoder = !isVaapiEncoder;
 05085            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05086            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 5087
 05088            var doVaVppTonemap = isVaapiDecoder && IsIntelVppTonemapAvailable(state, options);
 05089            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 05090            var doTonemap = doVaVppTonemap || doOclTonemap;
 05091            var doDeintH2645 = IsDeinterlaceAvailable(state);
 5092
 05093            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05094            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05095            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05096            var hasAssSubs = hasSubs
 05097                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05098                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 05099            var subW = state.SubtitleStream?.Width;
 05100            var subH = state.SubtitleStream?.Height;
 5101
 05102            var rotation = state.VideoStream?.Rotation ?? 0;
 05103            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05104            var doVaVppTranspose = !string.IsNullOrEmpty(transposeDir);
 05105            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVaVppTranspose));
 05106            var swpInW = swapWAndH ? inH : inW;
 05107            var swpInH = swapWAndH ? inW : inH;
 5108
 5109            /* Make main filters for video stream */
 05110            var mainFilters = new List<string>();
 5111
 05112            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 5113
 05114            if (isSwDecoder)
 5115            {
 5116                // INPUT sw surface(memory)
 5117                // sw deint
 05118                if (doDeintH2645)
 5119                {
 05120                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05121                    mainFilters.Add(swDeintFilter);
 5122                }
 5123
 05124                var outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05125                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05126                if (isMjpegEncoder && !doOclTonemap)
 5127                {
 5128                    // sw decoder + hw mjpeg encoder
 05129                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5130                }
 5131
 5132                // sw scale
 05133                mainFilters.Add(swScaleFilter);
 05134                mainFilters.Add($"format={outFormat}");
 5135
 5136                // keep video at memory except ocl tonemap,
 5137                // since the overhead caused by hwupload >>> using sw filter.
 5138                // sw => hw
 05139                if (doOclTonemap)
 5140                {
 05141                    mainFilters.Add("hwupload=derive_device=opencl");
 5142                }
 5143            }
 05144            else if (isVaapiDecoder)
 5145            {
 05146                var isRext = IsVideoStreamHevcRext(state);
 5147
 5148                // INPUT vaapi surface(vram)
 5149                // hw deint
 05150                if (doDeintH2645)
 5151                {
 05152                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05153                    mainFilters.Add(deintFilter);
 5154                }
 5155
 5156                // hw transpose
 05157                if (doVaVppTranspose)
 5158                {
 05159                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 5160                }
 5161
 05162                var outFormat = doTonemap ? (isRext ? "p010" : string.Empty) : "nv12";
 05163                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, swpInW, swpInH, reqW, reqH, req
 5164
 05165                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5166                {
 05167                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05168                    hwScaleFilter += ":mode=hq";
 5169                }
 5170
 5171                // allocate extra pool sizes for vaapi vpp
 05172                if (!string.IsNullOrEmpty(hwScaleFilter))
 5173                {
 05174                    hwScaleFilter += ":extra_hw_frames=24";
 5175                }
 5176
 5177                // hw scale
 05178                mainFilters.Add(hwScaleFilter);
 5179            }
 5180
 5181            // vaapi vpp tonemap
 05182            if (doVaVppTonemap && isVaapiDecoder)
 5183            {
 05184                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 05185                mainFilters.Add(tonemapFilter);
 5186            }
 5187
 05188            if (doOclTonemap && isVaapiDecoder)
 5189            {
 5190                // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05191                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 5192            }
 5193
 5194            // ocl tonemap
 05195            if (doOclTonemap)
 5196            {
 05197                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05198                mainFilters.Add(tonemapFilter);
 5199            }
 5200
 05201            if (doOclTonemap && isVaInVaOut)
 5202            {
 5203                // OUTPUT vaapi(nv12) surface(vram)
 5204                // reverse-mapping via vaapi-opencl interop.
 05205                mainFilters.Add("hwmap=derive_device=vaapi:mode=write:reverse=1");
 05206                mainFilters.Add("format=vaapi");
 5207            }
 5208
 05209            var memoryOutput = false;
 05210            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 05211            var isHwmapNotUsable = isUploadForOclTonemap && isVaapiEncoder;
 05212            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap)
 5213            {
 05214                memoryOutput = true;
 5215
 5216                // OUTPUT nv12 surface(memory)
 5217                // prefer hwmap to hwdownload on opencl/vaapi.
 05218                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap=mode=read");
 05219                mainFilters.Add("format=nv12");
 5220            }
 5221
 5222            // OUTPUT nv12 surface(memory)
 05223            if (isSwDecoder && isVaapiEncoder)
 5224            {
 05225                memoryOutput = true;
 5226            }
 5227
 05228            if (memoryOutput)
 5229            {
 5230                // text subtitles
 05231                if (hasTextSubs)
 5232                {
 05233                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05234                    mainFilters.Add(textSubtitlesFilter);
 5235                }
 5236            }
 5237
 05238            if (memoryOutput && isVaapiEncoder)
 5239            {
 05240                if (!hasGraphicalSubs)
 5241                {
 05242                    mainFilters.Add("hwupload_vaapi");
 5243                }
 5244            }
 5245
 5246            /* Make sub and overlay filters for subtitle stream */
 05247            var subFilters = new List<string>();
 05248            var overlayFilters = new List<string>();
 05249            if (isVaInVaOut)
 5250            {
 05251                if (hasSubs)
 5252                {
 05253                    if (hasGraphicalSubs)
 5254                    {
 5255                        // overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead
 05256                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 05257                        subFilters.Add(subPreProcFilters);
 05258                        subFilters.Add("format=bgra");
 5259                    }
 05260                    else if (hasTextSubs)
 5261                    {
 05262                        var framerate = state.VideoStream?.RealFrameRate;
 05263                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5264
 05265                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 05266                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05267                        subFilters.Add(alphaSrcFilter);
 05268                        subFilters.Add("format=bgra");
 05269                        subFilters.Add(subTextSubtitlesFilter);
 5270                    }
 5271
 05272                    subFilters.Add("hwupload=derive_device=vaapi");
 5273
 05274                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 05275                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 05276                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 05277                        : string.Empty;
 05278                    var overlayVaapiFilter = string.Format(
 05279                        CultureInfo.InvariantCulture,
 05280                        "overlay_vaapi=eof_action=pass:repeatlast=0{0}",
 05281                        overlaySize);
 05282                    overlayFilters.Add(overlayVaapiFilter);
 5283                }
 5284            }
 05285            else if (memoryOutput)
 5286            {
 05287                if (hasGraphicalSubs)
 5288                {
 05289                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05290                    subFilters.Add(subPreProcFilters);
 05291                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5292
 05293                    if (isVaapiEncoder)
 5294                    {
 05295                        overlayFilters.Add("hwupload_vaapi");
 5296                    }
 5297                }
 5298            }
 5299
 05300            return (mainFilters, subFilters, overlayFilters);
 5301        }
 5302
 5303        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVaapiFullVidFilter
 5304            EncodingJobInfo state,
 5305            EncodingOptions options,
 5306            string vidDecoder,
 5307            string vidEncoder)
 5308        {
 05309            var inW = state.VideoStream?.Width;
 05310            var inH = state.VideoStream?.Height;
 05311            var reqW = state.BaseRequest.Width;
 05312            var reqH = state.BaseRequest.Height;
 05313            var reqMaxW = state.BaseRequest.MaxWidth;
 05314            var reqMaxH = state.BaseRequest.MaxHeight;
 05315            var threeDFormat = state.MediaSource.Video3DFormat;
 5316
 05317            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05318            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05319            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05320            var isSwEncoder = !isVaapiEncoder;
 05321            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5322
 05323            var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
 05324            var doDeintH2645 = IsDeinterlaceAvailable(state);
 5325
 05326            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05327            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05328            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05329            var hasAssSubs = hasSubs
 05330                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05331                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5332
 05333            var rotation = state.VideoStream?.Rotation ?? 0;
 05334            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05335            var doVkTranspose = isVaapiDecoder && !string.IsNullOrEmpty(transposeDir);
 05336            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVkTranspose));
 05337            var swpInW = swapWAndH ? inH : inW;
 05338            var swpInH = swapWAndH ? inW : inH;
 5339
 5340            /* Make main filters for video stream */
 05341            var mainFilters = new List<string>();
 5342
 05343            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doVkTonemap));
 5344
 05345            if (isSwDecoder)
 5346            {
 5347                // INPUT sw surface(memory)
 5348                // sw deint
 05349                if (doDeintH2645)
 5350                {
 05351                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05352                    mainFilters.Add(swDeintFilter);
 5353                }
 5354
 05355                if (doVkTonemap || hasSubs)
 5356                {
 5357                    // sw => hw
 05358                    mainFilters.Add("hwupload=derive_device=vulkan");
 05359                    mainFilters.Add("format=vulkan");
 5360                }
 5361                else
 5362                {
 5363                    // sw scale
 05364                    var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW,
 05365                    mainFilters.Add(swScaleFilter);
 05366                    mainFilters.Add("format=nv12");
 5367                }
 5368            }
 05369            else if (isVaapiDecoder)
 5370            {
 5371                // INPUT vaapi surface(vram)
 05372                if (doVkTranspose || doVkTonemap || hasSubs)
 5373                {
 5374                    // map from vaapi to vulkan/drm via interop (Polaris/gfx8+).
 05375                    if (_mediaEncoder.EncoderVersion >= _minFFmpegAlteredVaVkInterop)
 5376                    {
 05377                        if (doVkTranspose || !_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5378                        {
 5379                            // disable the indirect va-drm-vk mapping since it's no longer reliable.
 05380                            mainFilters.Add("hwmap=derive_device=drm");
 05381                            mainFilters.Add("format=drm_prime");
 05382                            mainFilters.Add("hwmap=derive_device=vulkan");
 05383                            mainFilters.Add("format=vulkan");
 5384
 5385                            // workaround for libplacebo using the imported vulkan frame on gfx8.
 05386                            if (!_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5387                            {
 05388                                mainFilters.Add("scale_vulkan");
 5389                            }
 5390                        }
 05391                        else if (doVkTonemap || hasSubs)
 5392                        {
 5393                            // non ad-hoc libplacebo also accepts drm_prime direct input.
 05394                            mainFilters.Add("hwmap=derive_device=drm");
 05395                            mainFilters.Add("format=drm_prime");
 5396                        }
 5397                    }
 5398                    else // legacy va-vk mapping that works only in jellyfin-ffmpeg6
 5399                    {
 05400                        mainFilters.Add("hwmap=derive_device=vulkan");
 05401                        mainFilters.Add("format=vulkan");
 5402                    }
 5403                }
 5404                else
 5405                {
 5406                    // hw deint
 05407                    if (doDeintH2645)
 5408                    {
 05409                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05410                        mainFilters.Add(deintFilter);
 5411                    }
 5412
 5413                    // hw scale
 05414                    var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", "nv12", false, inW, inH, reqW, reqH, reqMaxW,
 5415
 05416                    if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder && !doVkTonemap)
 5417                    {
 05418                        hwScaleFilter += ":out_range=pc:mode=hq";
 5419                    }
 5420
 05421                    mainFilters.Add(hwScaleFilter);
 5422                }
 5423            }
 5424
 5425            // vk transpose
 05426            if (doVkTranspose)
 5427            {
 05428                if (string.Equals(transposeDir, "reversal", StringComparison.OrdinalIgnoreCase))
 5429                {
 05430                    mainFilters.Add("flip_vulkan");
 5431                }
 5432                else
 5433                {
 05434                    mainFilters.Add($"transpose_vulkan=dir={transposeDir}");
 5435                }
 5436            }
 5437
 5438            // vk libplacebo
 05439            if (doVkTonemap || hasSubs)
 5440            {
 05441                var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, req
 05442                mainFilters.Add(libplaceboFilter);
 05443                mainFilters.Add("format=vulkan");
 5444            }
 5445
 05446            if (doVkTonemap && !hasSubs)
 5447            {
 5448                // OUTPUT vaapi(nv12) surface(vram)
 5449                // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05450                mainFilters.Add("hwmap=derive_device=vaapi");
 05451                mainFilters.Add("format=vaapi");
 5452
 5453                // clear the surf->meta_offset and output nv12
 05454                mainFilters.Add("scale_vaapi=format=nv12");
 5455
 5456                // hw deint
 05457                if (doDeintH2645)
 5458                {
 05459                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05460                    mainFilters.Add(deintFilter);
 5461                }
 5462            }
 5463
 05464            if (!hasSubs)
 5465            {
 5466                // OUTPUT nv12 surface(memory)
 05467                if (isSwEncoder && (doVkTonemap || isVaapiDecoder))
 5468                {
 05469                    mainFilters.Add("hwdownload");
 05470                    mainFilters.Add("format=nv12");
 5471                }
 5472
 05473                if (isSwDecoder && isVaapiEncoder && !doVkTonemap)
 5474                {
 05475                    mainFilters.Add("hwupload_vaapi");
 5476                }
 5477            }
 5478
 5479            /* Make sub and overlay filters for subtitle stream */
 05480            var subFilters = new List<string>();
 05481            var overlayFilters = new List<string>();
 05482            if (hasSubs)
 5483            {
 05484                if (hasGraphicalSubs)
 5485                {
 05486                    var subW = state.SubtitleStream?.Width;
 05487                    var subH = state.SubtitleStream?.Height;
 05488                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05489                    subFilters.Add(subPreProcFilters);
 05490                    subFilters.Add("format=bgra");
 5491                }
 05492                else if (hasTextSubs)
 5493                {
 05494                    var framerate = state.VideoStream?.RealFrameRate;
 05495                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5496
 05497                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05498                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05499                    subFilters.Add(alphaSrcFilter);
 05500                    subFilters.Add("format=bgra");
 05501                    subFilters.Add(subTextSubtitlesFilter);
 5502                }
 5503
 05504                subFilters.Add("hwupload=derive_device=vulkan");
 05505                subFilters.Add("format=vulkan");
 5506
 05507                overlayFilters.Add("overlay_vulkan=eof_action=pass:repeatlast=0");
 5508
 05509                if (isSwEncoder)
 5510                {
 5511                    // OUTPUT nv12 surface(memory)
 05512                    overlayFilters.Add("scale_vulkan=format=nv12");
 05513                    overlayFilters.Add("hwdownload");
 05514                    overlayFilters.Add("format=nv12");
 5515                }
 05516                else if (isVaapiEncoder)
 5517                {
 5518                    // OUTPUT vaapi(nv12) surface(vram)
 5519                    // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05520                    overlayFilters.Add("hwmap=derive_device=vaapi");
 05521                    overlayFilters.Add("format=vaapi");
 5522
 5523                    // clear the surf->meta_offset and output nv12
 05524                    overlayFilters.Add("scale_vaapi=format=nv12");
 5525
 5526                    // hw deint
 05527                    if (doDeintH2645)
 5528                    {
 05529                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05530                        overlayFilters.Add(deintFilter);
 5531                    }
 5532                }
 5533            }
 5534
 05535            return (mainFilters, subFilters, overlayFilters);
 5536        }
 5537
 5538        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiLimitedVidFilter
 5539            EncodingJobInfo state,
 5540            EncodingOptions options,
 5541            string vidDecoder,
 5542            string vidEncoder)
 5543        {
 05544            var inW = state.VideoStream?.Width;
 05545            var inH = state.VideoStream?.Height;
 05546            var reqW = state.BaseRequest.Width;
 05547            var reqH = state.BaseRequest.Height;
 05548            var reqMaxW = state.BaseRequest.MaxWidth;
 05549            var reqMaxH = state.BaseRequest.MaxHeight;
 05550            var threeDFormat = state.MediaSource.Video3DFormat;
 5551
 05552            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05553            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05554            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05555            var isSwEncoder = !isVaapiEncoder;
 05556            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05557            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 05558            var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965;
 05559            var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd;
 5560
 05561            var doDeintH2645 = IsDeinterlaceAvailable(state);
 05562            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5563
 05564            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05565            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05566            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 5567
 05568            var rotation = state.VideoStream?.Rotation ?? 0;
 05569            var swapWAndH = Math.Abs(rotation) == 90 && isSwDecoder;
 05570            var swpInW = swapWAndH ? inH : inW;
 05571            var swpInH = swapWAndH ? inW : inH;
 5572
 5573            /* Make main filters for video stream */
 05574            var mainFilters = new List<string>();
 5575
 05576            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5577
 05578            var outFormat = string.Empty;
 05579            if (isSwDecoder)
 5580            {
 5581                // INPUT sw surface(memory)
 5582                // sw deint
 05583                if (doDeintH2645)
 5584                {
 05585                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05586                    mainFilters.Add(swDeintFilter);
 5587                }
 5588
 05589                outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05590                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05591                if (isMjpegEncoder && !doOclTonemap)
 5592                {
 5593                    // sw decoder + hw mjpeg encoder
 05594                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5595                }
 5596
 5597                // sw scale
 05598                mainFilters.Add(swScaleFilter);
 05599                mainFilters.Add("format=" + outFormat);
 5600
 5601                // keep video at memory except ocl tonemap,
 5602                // since the overhead caused by hwupload >>> using sw filter.
 5603                // sw => hw
 05604                if (doOclTonemap)
 5605                {
 05606                    mainFilters.Add("hwupload=derive_device=opencl");
 5607                }
 5608            }
 05609            else if (isVaapiDecoder)
 5610            {
 5611                // INPUT vaapi surface(vram)
 5612                // hw deint
 05613                if (doDeintH2645)
 5614                {
 05615                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05616                    mainFilters.Add(deintFilter);
 5617                }
 5618
 05619                outFormat = doOclTonemap ? string.Empty : "nv12";
 05620                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, inW, inH, reqW, reqH, reqMaxW, 
 5621
 05622                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5623                {
 05624                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05625                    hwScaleFilter += ":mode=hq";
 5626                }
 5627
 5628                // allocate extra pool sizes for vaapi vpp
 05629                if (!string.IsNullOrEmpty(hwScaleFilter))
 5630                {
 05631                    hwScaleFilter += ":extra_hw_frames=24";
 5632                }
 5633
 5634                // hw scale
 05635                mainFilters.Add(hwScaleFilter);
 5636            }
 5637
 05638            if (doOclTonemap && isVaapiDecoder)
 5639            {
 05640                if (isi965Driver)
 5641                {
 5642                    // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05643                    mainFilters.Add("hwmap=derive_device=opencl");
 5644                }
 5645                else
 5646                {
 05647                    mainFilters.Add("hwdownload");
 05648                    mainFilters.Add("format=p010le");
 05649                    mainFilters.Add("hwupload=derive_device=opencl");
 5650                }
 5651            }
 5652
 5653            // ocl tonemap
 05654            if (doOclTonemap)
 5655            {
 05656                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05657                mainFilters.Add(tonemapFilter);
 5658            }
 5659
 05660            if (doOclTonemap && isVaInVaOut)
 5661            {
 05662                if (isi965Driver)
 5663                {
 5664                    // OUTPUT vaapi(nv12) surface(vram)
 5665                    // reverse-mapping via vaapi-opencl interop.
 05666                    mainFilters.Add("hwmap=derive_device=vaapi:reverse=1");
 05667                    mainFilters.Add("format=vaapi");
 5668                }
 5669            }
 5670
 05671            var memoryOutput = false;
 05672            var isUploadForOclTonemap = doOclTonemap && (isSwDecoder || (isVaapiDecoder && !isi965Driver));
 05673            var isHwmapNotUsable = hasGraphicalSubs || isUploadForOclTonemap;
 05674            var isHwmapForSubs = hasSubs && isVaapiDecoder;
 05675            var isHwUnmapForTextSubs = hasTextSubs && isVaInVaOut && !isUploadForOclTonemap;
 05676            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap || isHwmapForSubs)
 5677            {
 05678                memoryOutput = true;
 5679
 5680                // OUTPUT nv12 surface(memory)
 5681                // prefer hwmap to hwdownload on opencl/vaapi.
 05682                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap");
 05683                mainFilters.Add("format=nv12");
 5684            }
 5685
 5686            // OUTPUT nv12 surface(memory)
 05687            if (isSwDecoder && isVaapiEncoder)
 5688            {
 05689                memoryOutput = true;
 5690            }
 5691
 05692            if (memoryOutput)
 5693            {
 5694                // text subtitles
 05695                if (hasTextSubs)
 5696                {
 05697                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05698                    mainFilters.Add(textSubtitlesFilter);
 5699                }
 5700            }
 5701
 05702            if (isHwUnmapForTextSubs)
 5703            {
 05704                mainFilters.Add("hwmap");
 05705                mainFilters.Add("format=vaapi");
 5706            }
 05707            else if (memoryOutput && isVaapiEncoder)
 5708            {
 05709                if (!hasGraphicalSubs)
 5710                {
 05711                    mainFilters.Add("hwupload_vaapi");
 5712                }
 5713            }
 5714
 5715            /* Make sub and overlay filters for subtitle stream */
 05716            var subFilters = new List<string>();
 05717            var overlayFilters = new List<string>();
 05718            if (memoryOutput)
 5719            {
 05720                if (hasGraphicalSubs)
 5721                {
 05722                    var subW = state.SubtitleStream?.Width;
 05723                    var subH = state.SubtitleStream?.Height;
 05724                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05725                    subFilters.Add(subPreProcFilters);
 05726                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5727
 05728                    if (isVaapiEncoder)
 5729                    {
 05730                        overlayFilters.Add("hwupload_vaapi");
 5731                    }
 5732                }
 5733            }
 5734
 05735            return (mainFilters, subFilters, overlayFilters);
 5736        }
 5737
 5738        /// <summary>
 5739        /// Gets the parameter of Apple VideoToolBox filter chain.
 5740        /// </summary>
 5741        /// <param name="state">Encoding state.</param>
 5742        /// <param name="options">Encoding options.</param>
 5743        /// <param name="vidEncoder">Video encoder to use.</param>
 5744        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5745        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFilterChain(
 5746            EncodingJobInfo state,
 5747            EncodingOptions options,
 5748            string vidEncoder)
 5749        {
 05750            if (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 5751            {
 05752                return (null, null, null);
 5753            }
 5754
 5755            // ReSharper disable once InconsistentNaming
 05756            var isMacOS = OperatingSystem.IsMacOS();
 05757            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05758            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05759            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05760            var isVtFullSupported = isMacOS && IsVideoToolboxFullSupported();
 5761
 5762            // legacy videotoolbox pipeline (disable hw filters)
 05763            if (!(isVtEncoder || isVtDecoder)
 05764                || !isVtFullSupported
 05765                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5766            {
 05767                return GetSwVidFilterChain(state, options, vidEncoder);
 5768            }
 5769
 5770            // preferred videotoolbox + metal filters pipeline
 05771            return GetAppleVidFiltersPreferred(state, options, vidDecoder, vidEncoder);
 5772        }
 5773
 5774        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFiltersPrefer
 5775            EncodingJobInfo state,
 5776            EncodingOptions options,
 5777            string vidDecoder,
 5778            string vidEncoder)
 5779        {
 05780            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05781            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05782            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5783
 05784            var inW = state.VideoStream?.Width;
 05785            var inH = state.VideoStream?.Height;
 05786            var reqW = state.BaseRequest.Width;
 05787            var reqH = state.BaseRequest.Height;
 05788            var reqMaxW = state.BaseRequest.MaxWidth;
 05789            var reqMaxH = state.BaseRequest.MaxHeight;
 05790            var threeDFormat = state.MediaSource.Video3DFormat;
 5791
 05792            var doDeintH2645 = IsDeinterlaceAvailable(state);
 05793            var doVtTonemap = IsVideoToolboxTonemapAvailable(state, options);
 05794            var doMetalTonemap = !doVtTonemap && IsHwTonemapAvailable(state, options);
 05795            var usingHwSurface = isVtDecoder && (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface);
 5796
 05797            var rotation = state.VideoStream?.Rotation ?? 0;
 05798            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05799            var doVtTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_vt");
 05800            var swapWAndH = Math.Abs(rotation) == 90 && doVtTranspose;
 05801            var swpInW = swapWAndH ? inH : inW;
 05802            var swpInH = swapWAndH ? inW : inH;
 5803
 05804            var scaleFormat = string.Empty;
 5805            // Use P010 for Metal tone mapping, otherwise force an 8bit output.
 05806            if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase))
 5807            {
 05808                if (doMetalTonemap)
 5809                {
 05810                    if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 5811                    {
 05812                        scaleFormat = "p010le";
 5813                    }
 5814                }
 5815                else
 5816                {
 05817                    scaleFormat = "nv12";
 5818                }
 5819            }
 5820
 05821            var hwScaleFilter = GetHwScaleFilter("scale", "vt", scaleFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW,
 5822
 05823            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05824            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05825            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05826            var hasAssSubs = hasSubs
 05827                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05828                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5829
 5830            /* Make main filters for video stream */
 05831            var mainFilters = new List<string>();
 5832
 5833            // hw deint
 05834            if (doDeintH2645)
 5835            {
 05836                var deintFilter = GetHwDeinterlaceFilter(state, options, "videotoolbox");
 05837                mainFilters.Add(deintFilter);
 5838            }
 5839
 5840            // hw transpose
 05841            if (doVtTranspose)
 5842            {
 05843                mainFilters.Add($"transpose_vt=dir={transposeDir}");
 5844            }
 5845
 05846            if (doVtTonemap)
 5847            {
 5848                const string VtTonemapArgs = "color_matrix=bt709:color_primaries=bt709:color_transfer=bt709";
 5849
 5850                // scale_vt can handle scaling & tonemapping in one shot, just like vpp_qsv.
 05851                hwScaleFilter = string.IsNullOrEmpty(hwScaleFilter)
 05852                    ? "scale_vt=" + VtTonemapArgs
 05853                    : hwScaleFilter + ":" + VtTonemapArgs;
 5854            }
 5855
 5856            // hw scale & vt tonemap
 05857            mainFilters.Add(hwScaleFilter);
 5858
 5859            // Metal tonemap
 05860            if (doMetalTonemap)
 5861            {
 05862                var tonemapFilter = GetHwTonemapFilter(options, "videotoolbox", "nv12", isMjpegEncoder);
 05863                mainFilters.Add(tonemapFilter);
 5864            }
 5865
 5866            /* Make sub and overlay filters for subtitle stream */
 05867            var subFilters = new List<string>();
 05868            var overlayFilters = new List<string>();
 5869
 05870            if (hasSubs)
 5871            {
 05872                if (hasGraphicalSubs)
 5873                {
 05874                    var subW = state.SubtitleStream?.Width;
 05875                    var subH = state.SubtitleStream?.Height;
 05876                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05877                    subFilters.Add(subPreProcFilters);
 05878                    subFilters.Add("format=bgra");
 5879                }
 05880                else if (hasTextSubs)
 5881                {
 05882                    var framerate = state.VideoStream?.RealFrameRate;
 05883                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5884
 05885                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05886                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05887                    subFilters.Add(alphaSrcFilter);
 05888                    subFilters.Add("format=bgra");
 05889                    subFilters.Add(subTextSubtitlesFilter);
 5890                }
 5891
 05892                subFilters.Add("hwupload");
 05893                overlayFilters.Add("overlay_videotoolbox=eof_action=pass:repeatlast=0");
 5894            }
 5895
 05896            if (usingHwSurface)
 5897            {
 05898                if (!isVtEncoder)
 5899                {
 05900                    mainFilters.Add("hwdownload");
 05901                    mainFilters.Add("format=nv12");
 5902                }
 5903
 05904                return (mainFilters, subFilters, overlayFilters);
 5905            }
 5906
 5907            // For old jellyfin-ffmpeg that has broken hwsurface, add a hwupload
 05908            var needFiltering = mainFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05909                                subFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05910                                overlayFilters.Any(f => !string.IsNullOrEmpty(f));
 05911            if (needFiltering)
 5912            {
 5913                // INPUT videotoolbox/memory surface(vram/uma)
 5914                // this will pass-through automatically if in/out format matches.
 05915                mainFilters.Insert(0, "hwupload");
 05916                mainFilters.Insert(0, "format=nv12|p010le|videotoolbox_vld");
 5917
 05918                if (!isVtEncoder)
 5919                {
 05920                    mainFilters.Add("hwdownload");
 05921                    mainFilters.Add("format=nv12");
 5922                }
 5923            }
 5924
 05925            return (mainFilters, subFilters, overlayFilters);
 5926        }
 5927
 5928        /// <summary>
 5929        /// Gets the parameter of Rockchip RKMPP/RKRGA filter chain.
 5930        /// </summary>
 5931        /// <param name="state">Encoding state.</param>
 5932        /// <param name="options">Encoding options.</param>
 5933        /// <param name="vidEncoder">Video encoder to use.</param>
 5934        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5935        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFilterChain(
 5936            EncodingJobInfo state,
 5937            EncodingOptions options,
 5938            string vidEncoder)
 5939        {
 05940            if (options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 5941            {
 05942                return (null, null, null);
 5943            }
 5944
 05945            var isLinux = OperatingSystem.IsLinux();
 05946            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05947            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05948            var isSwEncoder = !vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05949            var isRkmppOclSupported = isLinux && IsRkmppFullSupported() && IsOpenclFullSupported();
 5950
 05951            if ((isSwDecoder && isSwEncoder)
 05952                || !isRkmppOclSupported
 05953                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5954            {
 05955                return GetSwVidFilterChain(state, options, vidEncoder);
 5956            }
 5957
 5958            // preferred rkmpp + rkrga + opencl filters pipeline
 05959            if (isRkmppOclSupported)
 5960            {
 05961                return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5962            }
 5963
 05964            return (null, null, null);
 5965        }
 5966
 5967        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFiltersPrefer
 5968            EncodingJobInfo state,
 5969            EncodingOptions options,
 5970            string vidDecoder,
 5971            string vidEncoder)
 5972        {
 05973            var inW = state.VideoStream?.Width;
 05974            var inH = state.VideoStream?.Height;
 05975            var reqW = state.BaseRequest.Width;
 05976            var reqH = state.BaseRequest.Height;
 05977            var reqMaxW = state.BaseRequest.MaxWidth;
 05978            var reqMaxH = state.BaseRequest.MaxHeight;
 05979            var threeDFormat = state.MediaSource.Video3DFormat;
 5980
 05981            var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05982            var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05983            var isSwDecoder = !isRkmppDecoder;
 05984            var isSwEncoder = !isRkmppEncoder;
 05985            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05986            var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder;
 05987            var isEncoderSupportAfbc = isRkmppEncoder
 05988                && (vidEncoder.Contains("h264", StringComparison.OrdinalIgnoreCase)
 05989                    || vidEncoder.Contains("hevc", StringComparison.OrdinalIgnoreCase));
 5990
 05991            var doDeintH2645 = IsDeinterlaceAvailable(state);
 05992            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5993
 05994            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05995            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05996            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05997            var hasAssSubs = hasSubs
 05998                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05999                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 06000            var subW = state.SubtitleStream?.Width;
 06001            var subH = state.SubtitleStream?.Height;
 6002
 06003            var rotation = state.VideoStream?.Rotation ?? 0;
 06004            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 06005            var doRkVppTranspose = !string.IsNullOrEmpty(transposeDir);
 06006            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isRkmppDecoder && doRkVppTranspose));
 06007            var swpInW = swapWAndH ? inH : inW;
 06008            var swpInH = swapWAndH ? inW : inH;
 6009
 6010            /* Make main filters for video stream */
 06011            var mainFilters = new List<string>();
 6012
 06013            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 6014
 06015            if (isSwDecoder)
 6016            {
 6017                // INPUT sw surface(memory)
 6018                // sw deint
 06019                if (doDeintH2645)
 6020                {
 06021                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 06022                    mainFilters.Add(swDeintFilter);
 6023                }
 6024
 06025                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 06026                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 06027                if (isMjpegEncoder && !doOclTonemap)
 6028                {
 6029                    // sw decoder + hw mjpeg encoder
 06030                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 6031                }
 6032
 06033                if (!string.IsNullOrEmpty(swScaleFilter))
 6034                {
 06035                    swScaleFilter += ":flags=fast_bilinear";
 6036                }
 6037
 6038                // sw scale
 06039                mainFilters.Add(swScaleFilter);
 06040                mainFilters.Add($"format={outFormat}");
 6041
 6042                // keep video at memory except ocl tonemap,
 6043                // since the overhead caused by hwupload >>> using sw filter.
 6044                // sw => hw
 06045                if (doOclTonemap)
 6046                {
 06047                    mainFilters.Add("hwupload=derive_device=opencl");
 6048                }
 6049            }
 06050            else if (isRkmppDecoder)
 6051            {
 6052                // INPUT rkmpp/drm surface(gem/dma-heap)
 6053
 06054                var isFullAfbcPipeline = isEncoderSupportAfbc && isDrmInDrmOut && !doOclTonemap;
 06055                var swapOutputWandH = doRkVppTranspose && swapWAndH;
 06056                var outFormat = doOclTonemap ? "p010" : "nv12";
 06057                var hwScaleFilter = GetHwScaleFilter("vpp", "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, r
 06058                var doScaling = !string.IsNullOrEmpty(GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, sw
 6059
 06060                if (!hasSubs
 06061                     || doRkVppTranspose
 06062                     || !isFullAfbcPipeline
 06063                     || doScaling)
 6064                {
 06065                    var isScaleRatioSupported = IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 6066
 6067                    // RGA3 hardware only support (1/8 ~ 8) scaling in each blit operation,
 6068                    // but in Trickplay there's a case: (3840/320 == 12), enable 2pass for it
 06069                    if (doScaling && !isScaleRatioSupported)
 6070                    {
 6071                        // Vendor provided BSP kernel has an RGA driver bug that causes the output to be corrupted for P
 6072                        // Use NV15 instead of P010 to avoid the issue.
 6073                        // SDR inputs are using BGRA formats already which is not affected.
 06074                        var intermediateFormat = doOclTonemap ? "nv15" : (isMjpegEncoder ? "bgra" : outFormat);
 06075                        var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={intermediateFormat}:force_o
 06076                        mainFilters.Add(hwScaleFilterFirstPass);
 6077                    }
 6078
 6079                    // The RKMPP MJPEG encoder on some newer chip models no longer supports RGB input.
 6080                    // Use 2pass here to enable RGA output of full-range YUV in the 2nd pass.
 06081                    if (isMjpegEncoder && !doOclTonemap && ((doScaling && isScaleRatioSupported) || !doScaling))
 6082                    {
 06083                        var hwScaleFilterFirstPass = "vpp_rkrga=format=bgra:afbc=1";
 06084                        mainFilters.Add(hwScaleFilterFirstPass);
 6085                    }
 6086
 06087                    if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose)
 6088                    {
 06089                        hwScaleFilter += $":transpose={transposeDir}";
 6090                    }
 6091
 6092                    // try enabling AFBC to save DDR bandwidth
 06093                    if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline)
 6094                    {
 06095                        hwScaleFilter += ":afbc=1";
 6096                    }
 6097
 6098                    // hw transpose & scale
 06099                    mainFilters.Add(hwScaleFilter);
 6100                }
 6101            }
 6102
 06103            if (doOclTonemap && isRkmppDecoder)
 6104            {
 6105                // map from rkmpp/drm to opencl via drm-opencl interop.
 06106                mainFilters.Add("hwmap=derive_device=opencl");
 6107            }
 6108
 6109            // ocl tonemap
 06110            if (doOclTonemap)
 6111            {
 06112                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 06113                mainFilters.Add(tonemapFilter);
 6114            }
 6115
 06116            var memoryOutput = false;
 06117            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 06118            if ((isRkmppDecoder && isSwEncoder) || isUploadForOclTonemap)
 6119            {
 06120                memoryOutput = true;
 6121
 6122                // OUTPUT nv12 surface(memory)
 06123                mainFilters.Add("hwdownload");
 06124                mainFilters.Add("format=nv12");
 6125            }
 6126
 6127            // OUTPUT nv12 surface(memory)
 06128            if (isSwDecoder && isRkmppEncoder)
 6129            {
 06130                memoryOutput = true;
 6131            }
 6132
 06133            if (memoryOutput)
 6134            {
 6135                // text subtitles
 06136                if (hasTextSubs)
 6137                {
 06138                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 06139                    mainFilters.Add(textSubtitlesFilter);
 6140                }
 6141            }
 6142
 06143            if (isDrmInDrmOut)
 6144            {
 06145                if (doOclTonemap)
 6146                {
 6147                    // OUTPUT drm(nv12) surface(gem/dma-heap)
 6148                    // reverse-mapping via drm-opencl interop.
 06149                    mainFilters.Add("hwmap=derive_device=rkmpp:reverse=1");
 06150                    mainFilters.Add("format=drm_prime");
 6151                }
 6152            }
 6153
 6154            /* Make sub and overlay filters for subtitle stream */
 06155            var subFilters = new List<string>();
 06156            var overlayFilters = new List<string>();
 06157            if (isDrmInDrmOut)
 6158            {
 06159                if (hasSubs)
 6160                {
 06161                    var subMaxH = 1080;
 06162                    if (hasGraphicalSubs)
 6163                    {
 06164                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 06165                        subFilters.Add(subPreProcFilters);
 06166                        subFilters.Add("format=bgra");
 6167                    }
 06168                    else if (hasTextSubs)
 6169                    {
 06170                        var framerate = state.VideoStream?.RealFrameRate;
 06171                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 6172
 6173                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 06174                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, subMaxH, subF
 06175                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 06176                        subFilters.Add(alphaSrcFilter);
 06177                        subFilters.Add("format=bgra");
 06178                        subFilters.Add(subTextSubtitlesFilter);
 6179                    }
 6180
 06181                    subFilters.Add("hwupload=derive_device=rkmpp");
 6182
 6183                    // offload 1080p+ subtitles swscale upscaling from CPU to RGA
 06184                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 06185                    if (overlayW.HasValue && overlayH.HasValue && overlayH.Value > subMaxH)
 6186                    {
 06187                        subFilters.Add($"vpp_rkrga=w={overlayW.Value}:h={overlayH.Value}:format=bgra:afbc=1");
 6188                    }
 6189
 6190                    // try enabling AFBC to save DDR bandwidth
 06191                    var hwOverlayFilter = "overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12";
 06192                    if (isEncoderSupportAfbc)
 6193                    {
 06194                        hwOverlayFilter += ":afbc=1";
 6195                    }
 6196
 06197                    overlayFilters.Add(hwOverlayFilter);
 6198                }
 6199            }
 06200            else if (memoryOutput)
 6201            {
 06202                if (hasGraphicalSubs)
 6203                {
 06204                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 06205                    subFilters.Add(subPreProcFilters);
 06206                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 6207                }
 6208            }
 6209
 06210            return (mainFilters, subFilters, overlayFilters);
 6211        }
 6212
 6213        /// <summary>
 6214        /// Gets the parameter of video processing filters.
 6215        /// </summary>
 6216        /// <param name="state">Encoding state.</param>
 6217        /// <param name="options">Encoding options.</param>
 6218        /// <param name="outputVideoCodec">Video codec to use.</param>
 6219        /// <returns>The video processing filters parameter.</returns>
 6220        public string GetVideoProcessingFilterParam(
 6221            EncodingJobInfo state,
 6222            EncodingOptions options,
 6223            string outputVideoCodec)
 6224        {
 06225            var videoStream = state.VideoStream;
 06226            if (videoStream is null)
 6227            {
 06228                return string.Empty;
 6229            }
 6230
 06231            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 06232            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 06233            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 6234
 6235            List<string> mainFilters;
 6236            List<string> subFilters;
 6237            List<string> overlayFilters;
 6238
 06239            (mainFilters, subFilters, overlayFilters) = options.HardwareAccelerationType switch
 06240            {
 06241                HardwareAccelerationType.vaapi => GetVaapiVidFilterChain(state, options, outputVideoCodec),
 06242                HardwareAccelerationType.amf => GetAmdVidFilterChain(state, options, outputVideoCodec),
 06243                HardwareAccelerationType.qsv => GetIntelVidFilterChain(state, options, outputVideoCodec),
 06244                HardwareAccelerationType.nvenc => GetNvidiaVidFilterChain(state, options, outputVideoCodec),
 06245                HardwareAccelerationType.videotoolbox => GetAppleVidFilterChain(state, options, outputVideoCodec),
 06246                HardwareAccelerationType.rkmpp => GetRkmppVidFilterChain(state, options, outputVideoCodec),
 06247                _ => GetSwVidFilterChain(state, options, outputVideoCodec),
 06248            };
 6249
 06250            mainFilters?.RemoveAll(string.IsNullOrEmpty);
 06251            subFilters?.RemoveAll(string.IsNullOrEmpty);
 06252            overlayFilters?.RemoveAll(string.IsNullOrEmpty);
 6253
 06254            var framerate = GetFramerateParam(state);
 06255            if (mainFilters is not null && framerate.HasValue)
 6256            {
 06257                var doDeintH2645 = IsDeinterlaceAvailable(state);
 06258                var fpsFilter = string.Format(CultureInfo.InvariantCulture, "fps={0}", framerate.Value);
 6259
 6260                // For filter chain containing the deinterlace filter,
 6261                // place the fps filter at the end to preserve temporal info.
 06262                if (doDeintH2645)
 6263                {
 06264                    mainFilters.Add(fpsFilter);
 6265                }
 6266                else
 6267                {
 06268                    mainFilters.Insert(0, fpsFilter);
 6269                }
 6270            }
 6271
 06272            var mainStr = string.Empty;
 06273            if (mainFilters?.Count > 0)
 6274            {
 06275                mainStr = string.Format(
 06276                    CultureInfo.InvariantCulture,
 06277                    "{0}",
 06278                    string.Join(',', mainFilters));
 6279            }
 6280
 06281            if (overlayFilters?.Count == 0)
 6282            {
 6283                // -vf "scale..."
 06284                return string.IsNullOrEmpty(mainStr) ? string.Empty : " -vf \"" + mainStr + "\"";
 6285            }
 6286
 06287            if (overlayFilters?.Count > 0
 06288                && subFilters?.Count > 0
 06289                && state.SubtitleStream is not null)
 6290            {
 6291                // overlay graphical/text subtitles
 06292                var subStr = string.Format(
 06293                        CultureInfo.InvariantCulture,
 06294                        "{0}",
 06295                        string.Join(',', subFilters));
 6296
 06297                var overlayStr = string.Format(
 06298                        CultureInfo.InvariantCulture,
 06299                        "{0}",
 06300                        string.Join(',', overlayFilters));
 6301
 06302                var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
 06303                var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 06304                var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 6305
 06306                if (hasSubs)
 6307                {
 6308                    // -filter_complex "[0:s]scale=s[sub]..."
 06309                    var filterStr = string.IsNullOrEmpty(mainStr)
 06310                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]{5}\""
 06311                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6312
 06313                    if (hasTextSubs)
 6314                    {
 06315                        filterStr = string.IsNullOrEmpty(mainStr)
 06316                            ? " -filter_complex \"{4}[sub];[0:{2}][sub]{5}\""
 06317                            : " -filter_complex \"{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6318                    }
 6319
 06320                    return string.Format(
 06321                        CultureInfo.InvariantCulture,
 06322                        filterStr,
 06323                        mapPrefix,
 06324                        subtitleStreamIndex,
 06325                        videoStreamIndex,
 06326                        mainStr,
 06327                        subStr,
 06328                        overlayStr);
 6329                }
 6330            }
 6331
 06332            return string.Empty;
 6333        }
 6334
 6335        public string GetOverwriteColorPropertiesParam(EncodingJobInfo state, bool isTonemapAvailable)
 6336        {
 06337            if (isTonemapAvailable)
 6338            {
 06339                return GetInputHdrParam(state.VideoStream?.ColorTransfer);
 6340            }
 6341
 06342            return GetOutputSdrParam(null);
 6343        }
 6344
 6345        public string GetInputHdrParam(string colorTransfer)
 6346        {
 06347            if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
 6348            {
 6349                // HLG
 06350                return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
 6351            }
 6352
 6353            // HDR10
 06354            return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc";
 6355        }
 6356
 6357        public string GetOutputSdrParam(string tonemappingRange)
 6358        {
 6359            // SDR
 06360            if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
 6361            {
 06362                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
 6363            }
 6364
 06365            if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
 6366            {
 06367                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc";
 6368            }
 6369
 06370            return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
 6371        }
 6372
 6373        public static int GetVideoColorBitDepth(EncodingJobInfo state)
 6374        {
 06375            var videoStream = state.VideoStream;
 06376            if (videoStream is not null)
 6377            {
 06378                if (videoStream.BitDepth.HasValue)
 6379                {
 06380                    return videoStream.BitDepth.Value;
 6381                }
 6382
 06383                if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
 06384                    || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
 06385                    || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 06386                    || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
 6387                {
 06388                    return 8;
 6389                }
 6390
 06391                if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 06392                    || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 06393                    || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
 6394                {
 06395                    return 10;
 6396                }
 6397
 06398                if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 06399                    || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 06400                    || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
 6401                {
 06402                    return 12;
 6403                }
 6404
 06405                return 8;
 6406            }
 6407
 06408            return 0;
 6409        }
 6410
 6411        /// <summary>
 6412        /// Gets the ffmpeg option string for the hardware accelerated video decoder.
 6413        /// </summary>
 6414        /// <param name="state">The encoding job info.</param>
 6415        /// <param name="options">The encoding options.</param>
 6416        /// <returns>The option string or null if none available.</returns>
 6417        protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
 6418        {
 46419            var videoStream = state.VideoStream;
 46420            var mediaSource = state.MediaSource;
 46421            if (videoStream is null || mediaSource is null)
 6422            {
 06423                return null;
 6424            }
 6425
 6426            // HWA decoders can handle both video files and video folders.
 46427            var videoType = state.VideoType;
 46428            if (videoType != VideoType.VideoFile
 46429                && videoType != VideoType.Iso
 46430                && videoType != VideoType.Dvd
 46431                && videoType != VideoType.BluRay)
 6432            {
 06433                return null;
 6434            }
 6435
 46436            if (IsCopyCodec(state.OutputVideoCodec))
 6437            {
 06438                return null;
 6439            }
 6440
 46441            var hardwareAccelerationType = options.HardwareAccelerationType;
 6442
 46443            if (!string.IsNullOrEmpty(videoStream.Codec) && hardwareAccelerationType != HardwareAccelerationType.none)
 6444            {
 06445                var bitDepth = GetVideoColorBitDepth(state);
 6446
 6447                // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support for most platforms
 06448                if (bitDepth == 10
 06449                    && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06450                         || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
 06451                         || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)
 06452                         || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)))
 6453                {
 6454                    // RKMPP has H.264 Hi10P decoder
 06455                    bool hasHardwareHi10P = hardwareAccelerationType == HardwareAccelerationType.rkmpp;
 6456
 6457                    // VideoToolbox on Apple Silicon has H.264 Hi10P mode enabled after macOS 14.6
 06458                    if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox)
 6459                    {
 06460                        var ver = Environment.OSVersion.Version;
 06461                        var arch = RuntimeInformation.OSArchitecture;
 06462                        if (arch.Equals(Architecture.Arm64) && ver >= new Version(14, 6))
 6463                        {
 06464                            hasHardwareHi10P = true;
 6465                        }
 6466                    }
 6467
 06468                    if (!hasHardwareHi10P
 06469                        && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6470                    {
 06471                        return null;
 6472                    }
 6473                }
 6474
 6475                // Block unsupported H.264 Hi422P and Hi444PP profiles, which can be encoded with 4:2:0 pixel format
 06476                if (string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)
 06477                    && ((videoStream.Profile?.Contains("4:2:2", StringComparison.OrdinalIgnoreCase) ?? false)
 06478                        || (videoStream.Profile?.Contains("4:4:4", StringComparison.OrdinalIgnoreCase) ?? false)))
 6479                {
 6480                    // VideoToolbox on Apple Silicon has H.264 Hi444PP and theoretically also has Hi422P
 06481                    if (!(hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06482                          && RuntimeInformation.OSArchitecture.Equals(Architecture.Arm64)))
 6483                    {
 06484                        return null;
 6485                    }
 6486                }
 6487
 06488                var decoder = hardwareAccelerationType switch
 06489                {
 06490                    HardwareAccelerationType.vaapi => GetVaapiVidDecoder(state, options, videoStream, bitDepth),
 06491                    HardwareAccelerationType.amf => GetAmfVidDecoder(state, options, videoStream, bitDepth),
 06492                    HardwareAccelerationType.qsv => GetQsvHwVidDecoder(state, options, videoStream, bitDepth),
 06493                    HardwareAccelerationType.nvenc => GetNvdecVidDecoder(state, options, videoStream, bitDepth),
 06494                    HardwareAccelerationType.videotoolbox => GetVideotoolboxVidDecoder(state, options, videoStream, bitD
 06495                    HardwareAccelerationType.rkmpp => GetRkmppVidDecoder(state, options, videoStream, bitDepth),
 06496                    _ => string.Empty
 06497                };
 6498
 06499                if (!string.IsNullOrEmpty(decoder))
 6500                {
 06501                    return decoder;
 6502                }
 6503            }
 6504
 6505            // leave blank so ffmpeg will decide
 46506            return null;
 6507        }
 6508
 6509        /// <summary>
 6510        /// Gets a hw decoder name.
 6511        /// </summary>
 6512        /// <param name="options">Encoding options.</param>
 6513        /// <param name="decoderPrefix">Decoder prefix.</param>
 6514        /// <param name="decoderSuffix">Decoder suffix.</param>
 6515        /// <param name="videoCodec">Video codec to use.</param>
 6516        /// <param name="bitDepth">Video color bit depth.</param>
 6517        /// <returns>Hardware decoder name.</returns>
 6518        public string GetHwDecoderName(EncodingOptions options, string decoderPrefix, string decoderSuffix, string video
 6519        {
 06520            if (string.IsNullOrEmpty(decoderPrefix) || string.IsNullOrEmpty(decoderSuffix))
 6521            {
 06522                return null;
 6523            }
 6524
 06525            var decoderName = decoderPrefix + '_' + decoderSuffix;
 6526
 06527            var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains
 6528
 6529            // VideoToolbox decoders have built-in SW fallback
 06530            if (bitDepth == 10
 06531                && isCodecAvailable
 06532                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6533            {
 06534                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06535                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
 06536                    && !options.EnableDecodingColorDepth10Hevc)
 6537                {
 06538                    return null;
 6539                }
 6540
 06541                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06542                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06543                    && !options.EnableDecodingColorDepth10Vp9)
 6544                {
 06545                    return null;
 6546                }
 6547            }
 6548
 06549            if (string.Equals(decoderSuffix, "cuvid", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdec
 6550            {
 06551                return null;
 6552            }
 6553
 06554            if (string.Equals(decoderSuffix, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwD
 6555            {
 06556                return null;
 6557            }
 6558
 06559            if (string.Equals(decoderSuffix, "rkmpp", StringComparison.OrdinalIgnoreCase))
 6560            {
 06561                return null;
 6562            }
 6563
 06564            return isCodecAvailable ? (" -c:v " + decoderName) : null;
 6565        }
 6566
 6567        /// <summary>
 6568        /// Gets a hwaccel type to use as a hardware decoder depending on the system.
 6569        /// </summary>
 6570        /// <param name="state">Encoding state.</param>
 6571        /// <param name="options">Encoding options.</param>
 6572        /// <param name="videoCodec">Video codec to use.</param>
 6573        /// <param name="bitDepth">Video color bit depth.</param>
 6574        /// <param name="outputHwSurface">Specifies if output hw surface.</param>
 6575        /// <returns>Hardware accelerator type.</returns>
 6576        public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, int bitDepth, bo
 6577        {
 06578            var isWindows = OperatingSystem.IsWindows();
 06579            var isLinux = OperatingSystem.IsLinux();
 06580            var isMacOS = OperatingSystem.IsMacOS();
 06581            var isD3d11Supported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va");
 06582            var isVaapiSupported = isLinux && IsVaapiSupported(state);
 06583            var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported();
 06584            var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv");
 06585            var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
 06586            var isRkmppSupported = isLinux && IsRkmppFullSupported();
 06587            var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCas
 06588            var hardwareAccelerationType = options.HardwareAccelerationType;
 6589
 06590            var ffmpegVersion = _mediaEncoder.EncoderVersion;
 6591
 6592            // Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
 06593            var isAv1 = ffmpegVersion < _minFFmpegImplicitHwaccel
 06594                && string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
 6595
 6596            // Allow profile mismatch if decoding H.264 baseline with d3d11va and vaapi hwaccels.
 06597            var profileMismatch = string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
 06598                && string.Equals(state.VideoStream?.Profile, "baseline", StringComparison.OrdinalIgnoreCase);
 6599
 6600            // Disable the extra internal copy in nvdec. We already handle it in filter chain.
 06601            var nvdecNoInternalCopy = ffmpegVersion >= _minFFmpegHwaUnsafeOutput;
 6602
 6603            // Strip the display rotation side data from the transposed fmp4 output stream.
 06604            var stripRotationData = (state.VideoStream?.Rotation ?? 0) != 0
 06605                && ffmpegVersion >= _minFFmpegDisplayRotationOption;
 06606            var stripRotationDataArgs = stripRotationData ? " -display_rotation 0" : string.Empty;
 6607
 6608            // VideoToolbox decoders have built-in SW fallback
 06609            if (isCodecAvailable
 06610                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6611            {
 06612                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06613                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase))
 6614                {
 06615                    if (IsVideoStreamHevcRext(state))
 6616                    {
 06617                        if (bitDepth <= 10 && !options.EnableDecodingColorDepth10HevcRext)
 6618                        {
 06619                            return null;
 6620                        }
 6621
 06622                        if (bitDepth == 12 && !options.EnableDecodingColorDepth12HevcRext)
 6623                        {
 06624                            return null;
 6625                        }
 6626
 06627                        if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06628                            && !_mediaEncoder.IsVaapiDeviceInteliHD)
 6629                        {
 06630                            return null;
 6631                        }
 6632                    }
 06633                    else if (bitDepth == 10 && !options.EnableDecodingColorDepth10Hevc)
 6634                    {
 06635                        return null;
 6636                    }
 6637                }
 6638
 06639                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06640                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06641                    && bitDepth == 10
 06642                    && !options.EnableDecodingColorDepth10Vp9)
 6643                {
 06644                    return null;
 6645                }
 6646            }
 6647
 6648            // Intel qsv/d3d11va/vaapi
 06649            if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 6650            {
 06651                if (options.PreferSystemNativeHwDecoder)
 6652                {
 06653                    if (isVaapiSupported && isCodecAvailable)
 6654                    {
 06655                        return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + st
 06656                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " 
 6657                    }
 6658
 06659                    if (isD3d11Supported && isCodecAvailable)
 6660                    {
 06661                        return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + 
 06662                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 
 6663                    }
 6664                }
 6665                else
 6666                {
 06667                    if (isQsvSupported && isCodecAvailable)
 6668                    {
 06669                        return " -hwaccel qsv" + (outputHwSurface ? " -hwaccel_output_format qsv -noautorotate" + stripR
 6670                    }
 6671                }
 6672            }
 6673
 6674            // Nvidia cuda
 06675            if (hardwareAccelerationType == HardwareAccelerationType.nvenc)
 6676            {
 06677                if (isCudaSupported && isCodecAvailable)
 6678                {
 06679                    if (options.EnableEnhancedNvdecDecoder)
 6680                    {
 6681                        // set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support.
 06682                        return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stri
 06683                            + (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + 
 6684                    }
 6685
 6686                    // cuvid decoder doesn't have threading issue.
 06687                    return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stripRot
 6688                }
 6689            }
 6690
 6691            // Amd d3d11va
 06692            if (hardwareAccelerationType == HardwareAccelerationType.amf)
 6693            {
 06694                if (isD3d11Supported && isCodecAvailable)
 6695                {
 06696                    return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + stri
 06697                        + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 2" +
 6698                }
 6699            }
 6700
 6701            // Vaapi
 06702            if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06703                && isVaapiSupported
 06704                && isCodecAvailable)
 6705            {
 06706                return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + stripRotat
 06707                    + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1
 6708            }
 6709
 6710            // Apple videotoolbox
 06711            if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06712                && isVideotoolboxSupported
 06713                && isCodecAvailable)
 6714            {
 06715                return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string
 6716            }
 6717
 6718            // Rockchip rkmpp
 06719            if (hardwareAccelerationType == HardwareAccelerationType.rkmpp
 06720                && isRkmppSupported
 06721                && isCodecAvailable)
 6722            {
 06723                return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime -noautorotate" + stripR
 6724            }
 6725
 06726            return null;
 6727        }
 6728
 6729        public string GetQsvHwVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6730        {
 06731            var isWindows = OperatingSystem.IsWindows();
 06732            var isLinux = OperatingSystem.IsLinux();
 6733
 06734            if ((!isWindows && !isLinux)
 06735                || options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 6736            {
 06737                return null;
 6738            }
 6739
 06740            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 06741            var isIntelDx11OclSupported = isWindows
 06742                && _mediaEncoder.SupportsHwaccel("d3d11va")
 06743                && isQsvOclSupported;
 06744            var isIntelVaapiOclSupported = isLinux
 06745                && IsVaapiSupported(state)
 06746                && isQsvOclSupported;
 06747            var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
 06748                && _mediaEncoder.SupportsFilter("alphasrc");
 6749
 06750            var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06751                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06752            var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 06753            var is8_10_12bitSwFormatsQsv = is8_10bitSwFormatsQsv
 06754                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06755                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06756                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06757                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06758                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06759                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06760                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6761            // TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
 6762
 06763            if (is8bitSwFormatsQsv)
 6764            {
 06765                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06766                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6767                {
 06768                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6769                }
 6770
 06771                if (string.Equals(videoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase))
 6772                {
 06773                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6774                }
 6775
 06776                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6777                {
 06778                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6779                }
 6780
 06781                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6782                {
 06783                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6784                }
 6785            }
 6786
 06787            if (is8_10bitSwFormatsQsv)
 6788            {
 06789                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6790                {
 06791                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6792                }
 6793
 06794                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 6795                {
 06796                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6797                }
 6798            }
 6799
 06800            if (is8_10_12bitSwFormatsQsv)
 6801            {
 06802                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06803                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6804                {
 06805                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6806                }
 6807            }
 6808
 06809            return null;
 6810        }
 6811
 6812        public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6813        {
 06814            if ((!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
 06815                || options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 6816            {
 06817                return null;
 6818            }
 6819
 06820            var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
 06821            var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06822                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06823            var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06824            var is8_10_12bitSwFormatsNvdec = is8_10bitSwFormatsNvdec
 06825                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06826                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06827                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06828                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6829            // TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
 6830
 06831            if (is8bitSwFormatsNvdec)
 6832            {
 06833                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06834                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6835                {
 06836                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6837                }
 6838
 06839                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6840                {
 06841                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6842                }
 6843
 06844                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6845                {
 06846                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6847                }
 6848
 06849                if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6850                {
 06851                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpe
 6852                }
 6853
 06854                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6855                {
 06856                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6857                }
 6858            }
 6859
 06860            if (is8_10bitSwFormatsNvdec)
 6861            {
 06862                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6863                {
 06864                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6865                }
 6866
 06867                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6868                {
 06869                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6870                }
 6871            }
 6872
 06873            if (is8_10_12bitSwFormatsNvdec)
 6874            {
 06875                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06876                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6877                {
 06878                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6879                }
 6880            }
 6881
 06882            return null;
 6883        }
 6884
 6885        public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitD
 6886        {
 06887            if (!OperatingSystem.IsWindows()
 06888                || options.HardwareAccelerationType != HardwareAccelerationType.amf)
 6889            {
 06890                return null;
 6891            }
 6892
 06893            var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
 06894                && IsOpenclFullSupported()
 06895                && _mediaEncoder.SupportsFilter("alphasrc");
 06896            var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06897                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06898            var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 6899
 06900            if (is8bitSwFormatsAmf)
 6901            {
 06902                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06903                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6904                {
 06905                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6906                }
 6907
 06908                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6909                {
 06910                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6911                }
 6912
 06913                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6914                {
 06915                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6916                }
 6917            }
 6918
 06919            if (is8_10bitSwFormatsAmf)
 6920            {
 06921                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06922                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6923                {
 06924                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6925                }
 6926
 06927                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6928                {
 06929                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6930                }
 6931
 06932                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6933                {
 06934                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6935                }
 6936            }
 6937
 06938            return null;
 6939        }
 6940
 6941        public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6942        {
 06943            if (!OperatingSystem.IsLinux()
 06944                || options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 6945            {
 06946                return null;
 6947            }
 6948
 06949            var hwSurface = IsVaapiSupported(state)
 06950                && IsVaapiFullSupported()
 06951                && IsOpenclFullSupported()
 06952                && _mediaEncoder.SupportsFilter("alphasrc");
 06953            var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06954                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06955            var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06956            var is8_10_12bitSwFormatsVaapi = is8_10bitSwFormatsVaapi
 06957                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06958                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06959                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06960                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06961                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06962                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06963                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6964
 06965            if (is8bitSwFormatsVaapi)
 6966            {
 06967                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06968                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6969                {
 06970                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6971                }
 6972
 06973                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6974                {
 06975                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6976                }
 6977
 06978                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6979                {
 06980                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6981                }
 6982
 06983                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6984                {
 06985                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6986                }
 6987            }
 6988
 06989            if (is8_10bitSwFormatsVaapi)
 6990            {
 06991                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6992                {
 06993                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6994                }
 6995
 06996                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6997                {
 06998                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6999                }
 7000            }
 7001
 07002            if (is8_10_12bitSwFormatsVaapi)
 7003            {
 07004                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 07005                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7006                {
 07007                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 7008                }
 7009            }
 7010
 07011            return null;
 7012        }
 7013
 7014        public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream,
 7015        {
 07016            if (!OperatingSystem.IsMacOS()
 07017                || options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 7018            {
 07019                return null;
 7020            }
 7021
 07022            var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase
 07023                                    || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnore
 07024            var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, String
 07025            var is8_10_12bitSwFormatsVt = is8_10bitSwFormatsVt
 07026                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07027                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07028                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07029                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07030                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07031                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07032                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 07033            var isAv1SupportedSwFormatsVt = is8_10bitSwFormatsVt || string.Equals("yuv420p12le", videoStream.PixelFormat
 7034
 7035            // The related patches make videotoolbox hardware surface working is only available in jellyfin-ffmpeg 7.0.1
 07036            bool useHwSurface = (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface) && IsVideoToolboxFullSupp
 7037
 07038            if (is8bitSwFormatsVt)
 7039            {
 07040                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7041                {
 07042                    return GetHwaccelType(state, options, "vp8", bitDepth, useHwSurface);
 7043                }
 7044            }
 7045
 07046            if (is8_10bitSwFormatsVt)
 7047            {
 07048                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 07049                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7050                {
 07051                    return GetHwaccelType(state, options, "h264", bitDepth, useHwSurface);
 7052                }
 7053
 07054                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7055                {
 07056                    return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
 7057                }
 7058            }
 7059
 07060            if (is8_10_12bitSwFormatsVt)
 7061            {
 07062                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 07063                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7064                {
 07065                    return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
 7066                }
 7067
 07068                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 07069                    && isAv1SupportedSwFormatsVt
 07070                    && _mediaEncoder.IsVideoToolboxAv1DecodeAvailable)
 7071                {
 07072                    return GetHwaccelType(state, options, "av1", bitDepth, useHwSurface);
 7073                }
 7074            }
 7075
 07076            return null;
 7077        }
 7078
 7079        public string GetRkmppVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 7080        {
 07081            var isLinux = OperatingSystem.IsLinux();
 7082
 07083            if (!isLinux
 07084                || options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 7085            {
 07086                return null;
 7087            }
 7088
 07089            var inW = state.VideoStream?.Width;
 07090            var inH = state.VideoStream?.Height;
 07091            var reqW = state.BaseRequest.Width;
 07092            var reqH = state.BaseRequest.Height;
 07093            var reqMaxW = state.BaseRequest.MaxWidth;
 07094            var reqMaxH = state.BaseRequest.MaxHeight;
 7095
 7096            // rkrga RGA2e supports range from 1/16 to 16
 07097            if (!IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 16.0f))
 7098            {
 07099                return null;
 7100            }
 7101
 07102            var isRkmppOclSupported = IsRkmppFullSupported() && IsOpenclFullSupported();
 07103            var hwSurface = isRkmppOclSupported
 07104                && _mediaEncoder.SupportsFilter("alphasrc");
 7105
 7106            // rkrga RGA3 supports range from 1/8 to 8
 07107            var isAfbcSupported = hwSurface && IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 7108
 7109            // TODO: add more 8/10bit and 4:2:2 formats for Rkmpp after finishing the ffcheck tool
 07110            var is8bitSwFormatsRkmpp = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 07111                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 07112            var is10bitSwFormatsRkmpp = string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIg
 07113            var is8_10bitSwFormatsRkmpp = is8bitSwFormatsRkmpp || is10bitSwFormatsRkmpp;
 7114
 7115            // nv15 and nv20 are bit-stream only formats
 07116            if (is10bitSwFormatsRkmpp && !hwSurface)
 7117            {
 07118                return null;
 7119            }
 7120
 07121            if (is8bitSwFormatsRkmpp)
 7122            {
 07123                if (string.Equals(videoStream.Codec, "mpeg1video", StringComparison.OrdinalIgnoreCase))
 7124                {
 07125                    return GetHwaccelType(state, options, "mpeg1video", bitDepth, hwSurface);
 7126                }
 7127
 07128                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 7129                {
 07130                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 7131                }
 7132
 07133                if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 7134                {
 07135                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
 7136                }
 7137
 07138                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 7139                {
 07140                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 7141                }
 7142            }
 7143
 07144            if (is8_10bitSwFormatsRkmpp)
 7145            {
 07146                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 07147                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 7148                {
 07149                    var accelType = GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 07150                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7151                }
 7152
 07153                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 07154                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 7155                {
 07156                    var accelType = GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 07157                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7158                }
 7159
 07160                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 7161                {
 07162                    var accelType = GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 07163                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7164                }
 7165
 07166                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 7167                {
 7168                    // there's an issue about AV1 AFBC on RK3588, disable it for now until it's fixed upstream
 07169                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 7170                }
 7171            }
 7172
 07173            return null;
 7174        }
 7175
 7176        /// <summary>
 7177        /// Gets the number of threads.
 7178        /// </summary>
 7179        /// <param name="state">Encoding state.</param>
 7180        /// <param name="encodingOptions">Encoding options.</param>
 7181        /// <param name="outputVideoCodec">Video codec to use.</param>
 7182        /// <returns>Number of threads.</returns>
 7183#nullable enable
 7184        public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVide
 7185        {
 07186            var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount;
 7187
 07188            if (threads <= 0)
 7189            {
 7190                // Automatically set thread count
 07191                return 0;
 7192            }
 7193
 07194            return Math.Min(threads, Environment.ProcessorCount);
 7195        }
 7196
 7197#nullable disable
 7198        public void TryStreamCopy(EncodingJobInfo state, EncodingOptions options)
 7199        {
 07200            if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream))
 7201            {
 07202                state.OutputVideoCodec = "copy";
 7203            }
 7204            else
 7205            {
 07206                var user = state.User;
 7207
 7208                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07209                if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
 7210                {
 07211                    state.OutputVideoCodec = "copy";
 7212                }
 7213            }
 7214
 07215            var preventHlsAudioCopy = state.TranscodingType is TranscodingJobType.Hls
 07216                && state.VideoStream is not null
 07217                && !IsCopyCodec(state.OutputVideoCodec)
 07218                && options.HlsAudioSeekStrategy is HlsAudioSeekStrategy.TranscodeAudio;
 7219
 07220            if (state.AudioStream is not null
 07221                && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs)
 07222                && !preventHlsAudioCopy)
 7223            {
 07224                state.OutputAudioCodec = "copy";
 7225            }
 7226            else
 7227            {
 07228                var user = state.User;
 7229
 7230                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07231                if (user is not null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
 7232                {
 07233                    state.OutputAudioCodec = "copy";
 7234                }
 7235            }
 07236        }
 7237
 7238        private string GetFfmpegAnalyzeDurationArg(EncodingJobInfo state)
 7239        {
 47240            var analyzeDurationArgument = string.Empty;
 7241
 7242            // Apply -analyzeduration as per the environment variable,
 7243            // otherwise ffmpeg will break on certain files due to default value is 0.
 47244            var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
 7245
 47246            if (state.MediaSource.AnalyzeDurationMs > 0)
 7247            {
 07248                analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToStr
 7249            }
 47250            else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
 7251            {
 07252                analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
 7253            }
 7254
 47255            return analyzeDurationArgument;
 7256        }
 7257
 7258        private string GetFfmpegProbesizeArg()
 7259        {
 47260            var ffmpegProbeSize = _config.GetFFmpegProbeSize();
 7261
 47262            if (!string.IsNullOrEmpty(ffmpegProbeSize))
 7263            {
 07264                return $"-probesize {ffmpegProbeSize}";
 7265            }
 7266
 47267            return string.Empty;
 7268        }
 7269
 7270        public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
 7271        {
 07272            var inputModifier = string.Empty;
 07273            var analyzeDurationArgument = GetFfmpegAnalyzeDurationArg(state);
 7274
 07275            if (!string.IsNullOrEmpty(analyzeDurationArgument))
 7276            {
 07277                inputModifier += " " + analyzeDurationArgument;
 7278            }
 7279
 07280            inputModifier = inputModifier.Trim();
 7281
 7282            // Apply -probesize if configured
 07283            var ffmpegProbeSizeArgument = GetFfmpegProbesizeArg();
 7284
 07285            if (!string.IsNullOrEmpty(ffmpegProbeSizeArgument))
 7286            {
 07287                inputModifier += " " + ffmpegProbeSizeArgument;
 7288            }
 7289
 07290            var userAgentParam = GetUserAgentParam(state);
 7291
 07292            if (!string.IsNullOrEmpty(userAgentParam))
 7293            {
 07294                inputModifier += " " + userAgentParam;
 7295            }
 7296
 07297            inputModifier = inputModifier.Trim();
 7298
 07299            var refererParam = GetRefererParam(state);
 7300
 07301            if (!string.IsNullOrEmpty(refererParam))
 7302            {
 07303                inputModifier += " " + refererParam;
 7304            }
 7305
 07306            inputModifier = inputModifier.Trim();
 7307
 07308            inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions, segmentContainer);
 07309            inputModifier = inputModifier.Trim();
 7310
 07311            if (state.InputProtocol == MediaProtocol.Rtsp)
 7312            {
 07313                inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
 7314            }
 7315
 07316            if (!string.IsNullOrEmpty(state.InputAudioSync))
 7317            {
 07318                inputModifier += " -async " + state.InputAudioSync;
 7319            }
 7320
 7321            // The -fps_mode option cannot be applied to input
 07322            if (!string.IsNullOrEmpty(state.InputVideoSync) && _mediaEncoder.EncoderVersion < new Version(5, 1))
 7323            {
 07324                inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion);
 7325            }
 7326
 07327            int readrate = 0;
 07328            if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp)
 7329            {
 07330                readrate = 1;
 07331                inputModifier += " -re";
 7332            }
 07333            else if (encodingOptions.EnableSegmentDeletion
 07334                && state.VideoStream is not null
 07335                && state.TranscodingType == TranscodingJobType.Hls
 07336                && IsCopyCodec(state.OutputVideoCodec)
 07337                && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateOption)
 7338            {
 7339                // Set an input read rate limit 10x for using SegmentDeletion with stream-copy
 7340                // to prevent ffmpeg from exiting prematurely (due to fast drive)
 07341                readrate = 10;
 07342                inputModifier += $" -readrate {readrate}";
 7343            }
 7344
 7345            // Set a larger catchup value to revert to the old behavior,
 7346            // otherwise, remuxing might stall due to this new option
 07347            if (readrate > 0 && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateCatchupOption)
 7348            {
 07349                inputModifier += $" -readrate_catchup {readrate * 100}";
 7350            }
 7351
 07352            var flags = new List<string>();
 07353            if (state.IgnoreInputDts)
 7354            {
 07355                flags.Add("+igndts");
 7356            }
 7357
 07358            if (state.IgnoreInputIndex)
 7359            {
 07360                flags.Add("+ignidx");
 7361            }
 7362
 07363            if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
 7364            {
 07365                flags.Add("+genpts");
 7366            }
 7367
 07368            if (state.DiscardCorruptFramesInput)
 7369            {
 07370                flags.Add("+discardcorrupt");
 7371            }
 7372
 07373            if (state.EnableFastSeekInput)
 7374            {
 07375                flags.Add("+fastseek");
 7376            }
 7377
 07378            if (flags.Count > 0)
 7379            {
 07380                inputModifier += " -fflags " + string.Join(string.Empty, flags);
 7381            }
 7382
 07383            if (state.IsVideoRequest)
 7384            {
 07385                if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && encodingOpt
 7386                {
 07387                    var inputFormat = GetInputFormat(state.InputContainer);
 07388                    if (!string.IsNullOrEmpty(inputFormat))
 7389                    {
 07390                        inputModifier += " -f " + inputFormat;
 7391                    }
 7392                }
 7393            }
 7394
 07395            if (state.MediaSource.RequiresLooping)
 7396            {
 07397                inputModifier += " -stream_loop -1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2";
 7398            }
 7399
 07400            return inputModifier;
 7401        }
 7402
 7403        public void AttachMediaSourceInfo(
 7404            EncodingJobInfo state,
 7405            EncodingOptions encodingOptions,
 7406            MediaSourceInfo mediaSource,
 7407            string requestedUrl)
 7408        {
 07409            ArgumentNullException.ThrowIfNull(state);
 7410
 07411            ArgumentNullException.ThrowIfNull(mediaSource);
 7412
 07413            var path = mediaSource.Path;
 07414            var protocol = mediaSource.Protocol;
 7415
 07416            if (!string.IsNullOrEmpty(mediaSource.EncoderPath) && mediaSource.EncoderProtocol.HasValue)
 7417            {
 07418                path = mediaSource.EncoderPath;
 07419                protocol = mediaSource.EncoderProtocol.Value;
 7420            }
 7421
 07422            state.MediaPath = path;
 07423            state.InputProtocol = protocol;
 07424            state.InputContainer = mediaSource.Container;
 07425            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07426            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 7427
 07428            state.IsoType = mediaSource.IsoType;
 7429
 07430            if (mediaSource.Timestamp.HasValue)
 7431            {
 07432                state.InputTimestamp = mediaSource.Timestamp.Value;
 7433            }
 7434
 07435            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07436            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 07437            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
 7438
 07439            if ((state.ReadInputAtNativeFramerate && !state.IsSegmentedLiveStream)
 07440                || (mediaSource.Protocol == MediaProtocol.File
 07441                && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)))
 7442            {
 07443                state.InputVideoSync = "-1";
 07444                state.InputAudioSync = "1";
 7445            }
 7446
 07447            if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase)
 07448                || string.Equals(mediaSource.Container, "asf", StringComparison.OrdinalIgnoreCase))
 7449            {
 7450                // Seeing some stuttering when transcoding wma to audio-only HLS
 07451                state.InputAudioSync = "1";
 7452            }
 7453
 07454            var mediaStreams = mediaSource.MediaStreams;
 7455
 07456            if (state.IsVideoRequest)
 7457            {
 07458                var videoRequest = state.BaseRequest;
 7459
 07460                if (string.IsNullOrEmpty(videoRequest.VideoCodec))
 7461                {
 07462                    if (string.IsNullOrEmpty(requestedUrl))
 7463                    {
 07464                        requestedUrl = "test." + videoRequest.Container;
 7465                    }
 7466
 07467                    videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
 7468                }
 7469
 07470                state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
 07471                state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Su
 07472                state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
 07473                state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
 7474
 07475                if (state.SubtitleStream is not null && !state.SubtitleStream.IsExternal)
 7476                {
 07477                    state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !
 7478                }
 7479
 07480                EnforceResolutionLimit(state);
 7481
 07482                NormalizeSubtitleEmbed(state);
 7483            }
 7484            else
 7485            {
 07486                state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
 7487            }
 7488
 07489            state.MediaSource = mediaSource;
 7490
 07491            var request = state.BaseRequest;
 07492            var supportedAudioCodecs = state.SupportedAudioCodecs;
 07493            if (request is not null && supportedAudioCodecs is not null && supportedAudioCodecs.Length > 0)
 7494            {
 07495                var supportedAudioCodecsList = supportedAudioCodecs.ToList();
 7496
 07497                ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
 7498
 07499                state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray();
 7500
 07501                request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(_mediaEncoder.CanEncodeToAudioCodec)
 07502                    ?? state.SupportedAudioCodecs.FirstOrDefault();
 7503            }
 7504
 07505            var supportedVideoCodecs = state.SupportedVideoCodecs;
 07506            if (request is not null && supportedVideoCodecs is not null && supportedVideoCodecs.Length > 0)
 7507            {
 07508                var supportedVideoCodecsList = supportedVideoCodecs.ToList();
 7509
 07510                ShiftVideoCodecsIfNeeded(supportedVideoCodecsList, encodingOptions);
 7511
 07512                state.SupportedVideoCodecs = supportedVideoCodecsList.ToArray();
 7513
 07514                request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
 7515            }
 07516        }
 7517
 7518        private void ShiftAudioCodecsIfNeeded(List<string> audioCodecs, MediaStream audioStream)
 7519        {
 7520            // No need to shift if there is only one supported audio codec.
 07521            if (audioCodecs.Count < 2)
 7522            {
 07523                return;
 7524            }
 7525
 07526            var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
 07527            var shiftAudioCodecs = new List<string>();
 07528            if (inputChannels >= 6)
 7529            {
 7530                // DTS and TrueHD are not supported by HLS
 7531                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07532                shiftAudioCodecs.Add("dts");
 07533                shiftAudioCodecs.Add("truehd");
 7534            }
 7535            else
 7536            {
 7537                // Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
 7538                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07539                shiftAudioCodecs.Add("ac3");
 07540                shiftAudioCodecs.Add("eac3");
 7541            }
 7542
 07543            if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7544            {
 07545                return;
 7546            }
 7547
 07548            while (shiftAudioCodecs.Contains(audioCodecs[0], StringComparison.OrdinalIgnoreCase))
 7549            {
 07550                var removed = audioCodecs[0];
 07551                audioCodecs.RemoveAt(0);
 07552                audioCodecs.Add(removed);
 7553            }
 07554        }
 7555
 7556        private void ShiftVideoCodecsIfNeeded(List<string> videoCodecs, EncodingOptions encodingOptions)
 7557        {
 7558            // No need to shift if there is only one supported video codec.
 07559            if (videoCodecs.Count < 2)
 7560            {
 07561                return;
 7562            }
 7563
 7564            // Shift codecs to the end of list if it's not allowed.
 07565            var shiftVideoCodecs = new List<string>();
 07566            if (!encodingOptions.AllowHevcEncoding)
 7567            {
 07568                shiftVideoCodecs.Add("hevc");
 07569                shiftVideoCodecs.Add("h265");
 7570            }
 7571
 07572            if (!encodingOptions.AllowAv1Encoding)
 7573            {
 07574                shiftVideoCodecs.Add("av1");
 7575            }
 7576
 07577            if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7578            {
 07579                return;
 7580            }
 7581
 07582            while (shiftVideoCodecs.Contains(videoCodecs[0], StringComparison.OrdinalIgnoreCase))
 7583            {
 07584                var removed = videoCodecs[0];
 07585                videoCodecs.RemoveAt(0);
 07586                videoCodecs.Add(removed);
 7587            }
 07588        }
 7589
 7590        private void NormalizeSubtitleEmbed(EncodingJobInfo state)
 7591        {
 07592            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7593            {
 07594                return;
 7595            }
 7596
 7597            // This is tricky to remux in, after converting to dvdsub it's not positioned correctly
 7598            // Therefore, let's just burn it in
 07599            if (string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 7600            {
 07601                state.SubtitleDeliveryMethod = SubtitleDeliveryMethod.Encode;
 7602            }
 07603        }
 7604
 7605        public string GetSubtitleEmbedArguments(EncodingJobInfo state)
 7606        {
 07607            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7608            {
 07609                return string.Empty;
 7610            }
 7611
 07612            var format = state.SupportedSubtitleCodecs.FirstOrDefault();
 7613            string codec;
 7614
 07615            if (string.IsNullOrEmpty(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.Ordin
 7616            {
 07617                codec = "copy";
 7618            }
 7619            else
 7620            {
 07621                codec = format;
 7622            }
 7623
 07624            return " -codec:s:0 " + codec + " -disposition:s:0 default";
 7625        }
 7626
 7627        public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, Encoder
 7628        {
 7629            // Get the output codec name
 07630            var videoCodec = GetVideoEncoder(state, encodingOptions);
 7631
 07632            var format = string.Empty;
 07633            var keyFrame = string.Empty;
 07634            var outputPath = state.OutputFilePath;
 7635
 07636            if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase)
 07637                && state.BaseRequest.Context == EncodingContext.Streaming)
 7638            {
 7639                // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
 07640                format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov";
 7641            }
 7642
 07643            var threads = GetNumberOfThreads(state, encodingOptions, videoCodec);
 7644
 07645            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7646
 07647            return string.Format(
 07648                CultureInfo.InvariantCulture,
 07649                "{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
 07650                inputModifier,
 07651                GetInputArgument(state, encodingOptions, null),
 07652                keyFrame,
 07653                GetMapArgs(state),
 07654                GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset),
 07655                threads,
 07656                GetProgressiveVideoAudioArguments(state, encodingOptions),
 07657                GetSubtitleEmbedArguments(state),
 07658                format,
 07659                outputPath).Trim();
 7660        }
 7661
 7662        public string GetOutputFFlags(EncodingJobInfo state)
 7663        {
 07664            var flags = new List<string>();
 07665            if (state.GenPtsOutput)
 7666            {
 07667                flags.Add("+genpts");
 7668            }
 7669
 07670            if (flags.Count > 0)
 7671            {
 07672                return " -fflags " + string.Join(string.Empty, flags);
 7673            }
 7674
 07675            return string.Empty;
 7676        }
 7677
 7678        public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoC
 7679        {
 07680            var args = "-codec:v:0 " + videoCodec;
 7681
 07682            if (state.BaseRequest.EnableMpegtsM2TsMode)
 7683            {
 07684                args += " -mpegts_m2ts_mode 1";
 7685            }
 7686
 07687            if (IsCopyCodec(videoCodec))
 7688            {
 07689                if (state.VideoStream is not null
 07690                    && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
 07691                    && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
 7692                {
 07693                    string bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Video);
 07694                    if (!string.IsNullOrEmpty(bitStreamArgs))
 7695                    {
 07696                        args += " " + bitStreamArgs;
 7697                    }
 7698                }
 7699
 07700                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7701                {
 07702                    args += " -copyts -avoid_negative_ts disabled -start_at_zero";
 7703                }
 7704
 07705                if (!state.RunTimeTicks.HasValue)
 7706                {
 07707                    args += " -fflags +genpts";
 7708                }
 7709            }
 7710            else
 7711            {
 07712                var keyFrameArg = string.Format(
 07713                    CultureInfo.InvariantCulture,
 07714                    " -force_key_frames \"expr:gte(t,n_forced*{0})\"",
 07715                    5);
 7716
 07717                args += keyFrameArg;
 7718
 07719                var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream &&
 7720
 07721                var hasCopyTs = false;
 7722
 7723                // video processing filters.
 07724                var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
 7725
 07726                var negativeMapArgs = GetNegativeMapArgsByFilters(state, videoProcessParam);
 7727
 07728                args = negativeMapArgs + args + videoProcessParam;
 7729
 07730                hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
 7731
 07732                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7733                {
 07734                    if (!hasCopyTs)
 7735                    {
 07736                        args += " -copyts";
 7737                    }
 7738
 07739                    args += " -avoid_negative_ts disabled";
 7740
 07741                    if (!(state.SubtitleStream is not null && state.SubtitleStream.IsExternal && !state.SubtitleStream.I
 7742                    {
 07743                        args += " -start_at_zero";
 7744                    }
 7745                }
 7746
 07747                var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset);
 7748
 07749                if (!string.IsNullOrEmpty(qualityParam))
 7750                {
 07751                    args += " " + qualityParam.Trim();
 7752                }
 7753            }
 7754
 07755            if (!string.IsNullOrEmpty(state.OutputVideoSync))
 7756            {
 07757                args += GetVideoSyncOption(state.OutputVideoSync, _mediaEncoder.EncoderVersion);
 7758            }
 7759
 07760            args += GetOutputFFlags(state);
 7761
 07762            return args;
 7763        }
 7764
 7765        public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions)
 7766        {
 7767            // If the video doesn't have an audio stream, return a default.
 07768            if (state.AudioStream is null && state.VideoStream is not null)
 7769            {
 07770                return string.Empty;
 7771            }
 7772
 7773            // Get the output codec name
 07774            var codec = GetAudioEncoder(state);
 7775
 07776            var args = "-codec:a:0 " + codec;
 7777
 07778            if (IsCopyCodec(codec))
 7779            {
 07780                return args;
 7781            }
 7782
 07783            var channels = state.OutputAudioChannels;
 7784
 07785            var useDownMixAlgorithm = state.AudioStream is not null
 07786                                      && DownMixAlgorithmsHelper.AlgorithmFilterStrings.ContainsKey((encodingOptions.Dow
 7787
 07788            if (channels.HasValue && !useDownMixAlgorithm)
 7789            {
 07790                args += " -ac " + channels.Value;
 7791            }
 7792
 07793            var bitrate = state.OutputAudioBitrate;
 07794            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
 7795            {
 07796                var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2);
 07797                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7798                {
 07799                    args += vbrParam;
 7800                }
 7801                else
 7802                {
 07803                    args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
 7804                }
 7805            }
 7806
 07807            if (state.OutputAudioSampleRate.HasValue)
 7808            {
 07809                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
 7810            }
 7811
 07812            args += GetAudioFilterParam(state, encodingOptions);
 7813
 07814            return args;
 7815        }
 7816
 7817        public string GetProgressiveAudioFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string 
 7818        {
 07819            var audioTranscodeParams = new List<string>();
 7820
 07821            var bitrate = state.OutputAudioBitrate;
 07822            var channels = state.OutputAudioChannels;
 07823            var outputCodec = state.OutputAudioCodec;
 7824
 07825            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
 7826            {
 07827                var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2);
 07828                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7829                {
 07830                    audioTranscodeParams.Add(vbrParam);
 7831                }
 7832                else
 7833                {
 07834                    audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
 7835                }
 7836            }
 7837
 07838            if (channels.HasValue)
 7839            {
 07840                audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)
 7841            }
 7842
 07843            if (!string.IsNullOrEmpty(outputCodec))
 7844            {
 07845                audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
 7846            }
 7847
 07848            if (GetAudioEncoder(state).StartsWith("pcm_", StringComparison.Ordinal))
 7849            {
 07850                audioTranscodeParams.Add(string.Concat("-f ", GetAudioEncoder(state).AsSpan(4)));
 07851                audioTranscodeParams.Add("-ar " + state.BaseRequest.AudioBitRate);
 7852            }
 7853
 07854            if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
 7855            {
 7856                // opus only supports specific sampling rates
 07857                var sampleRate = state.OutputAudioSampleRate;
 07858                if (sampleRate.HasValue)
 7859                {
 07860                    var sampleRateValue = sampleRate.Value switch
 07861                    {
 07862                        <= 8000 => 8000,
 07863                        <= 12000 => 12000,
 07864                        <= 16000 => 16000,
 07865                        <= 24000 => 24000,
 07866                        _ => 48000
 07867                    };
 7868
 07869                    audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
 7870                }
 7871            }
 7872
 7873            // Copy the movflags from GetProgressiveVideoFullCommandLine
 7874            // See #9248 and the associated PR for why this is needed
 07875            if (_mp4ContainerNames.Contains(state.OutputContainer))
 7876            {
 07877                audioTranscodeParams.Add("-movflags empty_moov+delay_moov");
 7878            }
 7879
 07880            var threads = GetNumberOfThreads(state, encodingOptions, null);
 7881
 07882            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7883
 07884            return string.Format(
 07885                CultureInfo.InvariantCulture,
 07886                "{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"",
 07887                inputModifier,
 07888                GetInputArgument(state, encodingOptions, null),
 07889                threads,
 07890                " -vn",
 07891                string.Join(' ', audioTranscodeParams),
 07892                outputPath,
 07893                string.Empty,
 07894                string.Empty,
 07895                string.Empty).Trim();
 7896        }
 7897
 7898        public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
 7899        {
 167900            var index = 0;
 167901            var length = mediaStreams.Count;
 7902
 567903            for (var i = 0; i < length; i++)
 7904            {
 287905                var currentMediaStream = mediaStreams[i];
 287906                if (currentMediaStream == streamToFind)
 7907                {
 167908                    return index;
 7909                }
 7910
 127911                if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
 7912                {
 127913                    index++;
 7914                }
 7915            }
 7916
 07917            return -1;
 7918        }
 7919
 7920        public static bool IsCopyCodec(string codec)
 7921        {
 297922            return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
 7923        }
 7924
 7925        private static bool ShouldEncodeSubtitle(EncodingJobInfo state)
 7926        {
 107927            return state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
 107928                   || (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec));
 7929        }
 7930
 7931        private static bool NeedsExternalSubtitleMuxing(EncodingJobInfo state)
 7932        {
 47933            return state.SubtitleStream is not null
 47934                && state.SubtitleStream.IsExternal
 47935                && (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed
 47936                    || (ShouldEncodeSubtitle(state) && !state.SubtitleStream.IsTextSubtitleStream));
 7937        }
 7938
 7939        public static string GetVideoSyncOption(string videoSync, Version encoderVersion)
 7940        {
 07941            if (string.IsNullOrEmpty(videoSync))
 7942            {
 07943                return string.Empty;
 7944            }
 7945
 07946            if (encoderVersion >= new Version(5, 1))
 7947            {
 07948                if (int.TryParse(videoSync, CultureInfo.InvariantCulture, out var vsync))
 7949                {
 07950                    return vsync switch
 07951                    {
 07952                        -1 => " -fps_mode auto",
 07953                        0 => " -fps_mode passthrough",
 07954                        1 => " -fps_mode cfr",
 07955                        2 => " -fps_mode vfr",
 07956                        _ => string.Empty
 07957                    };
 7958                }
 7959
 07960                return string.Empty;
 7961            }
 7962
 7963            // -vsync is deprecated in FFmpeg 5.1 and will be removed in the future.
 07964            return $" -vsync {videoSync}";
 7965        }
 7966    }
 7967}

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)
IsDeinterlaceAvailable(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
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)
GetCopiedAudioTrimBsf(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetSegmentFileExtension(System.String)
GetVideoBitrateParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.String)
GetEncoderParam(System.Nullable`1<MediaBrowser.Model.Entities.EncoderPreset>,MediaBrowser.Model.Entities.EncoderPreset,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.Boolean)
NormalizeTranscodingLevel(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.String)
GetTextSubtitlesFilter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.Boolean,System.Boolean)
GetFramerateParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetHlsVideoKeyFrameArguments(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.String,System.Int32,System.Boolean,System.Nullable`1<System.Int32>)
GetVideoQualityParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.String,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.EncoderPreset)
CanStreamCopyVideo(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Entities.MediaStream)
CanStreamCopyAudio(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Entities.MediaStream,System.Collections.Generic.IEnumerable`1<System.String>)
GetVideoBitrateParamValue(MediaBrowser.Controller.MediaEncoding.BaseEncodingJobOptions,MediaBrowser.Model.Entities.MediaStream,System.String)
GetMinBitrate(System.Int32,System.Int32)
GetVideoBitrateScaleFactor(System.String)
ScaleBitrate(System.Int32,System.String,System.String)
GetAudioBitrateParam(MediaBrowser.Controller.MediaEncoding.BaseEncodingJobOptions,MediaBrowser.Model.Entities.MediaStream,System.Nullable`1<System.Int32>)
GetAudioBitrateParam(System.Nullable`1<System.Int32>,System.String,MediaBrowser.Model.Entities.MediaStream,System.Nullable`1<System.Int32>)
GetAudioVbrModeParam(System.String,System.Int32,System.Int32)
GetAudioFilterParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetNumAudioChannelsParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Entities.MediaStream,System.String)
EnforceResolutionLimit(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetFastSeekCommandLineParameter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetMapArgs(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetNegativeMapArgsByFilters(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.String)
GetMediaStream(System.Collections.Generic.IEnumerable`1<MediaBrowser.Model.Entities.MediaStream>,System.Nullable`1<System.Int32>,MediaBrowser.Model.Entities.MediaStreamType,System.Boolean)
GetFixedOutputSize(System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>)
IsScaleRatioSupported(System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Double>)
GetHwScaleFilter(System.String,System.String,System.String,System.Boolean,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>)
GetGraphicalSubPreProcessFilters(System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>)
GetAlphaSrcFilter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Single>)
GetSwScaleFilter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<MediaBrowser.Model.Entities.Video3DFormat>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>)
GetFixedSwScaleFilter(System.Nullable`1<MediaBrowser.Model.Entities.Video3DFormat>,System.Int32,System.Int32)
GetSwDeinterlaceFilter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetHwDeinterlaceFilter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetHwTonemapFilter(MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String,System.Boolean)
GetLibplaceboFilter(MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.Boolean,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Boolean)
GetVideoTransposeDirection(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetSwVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetNvidiaVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetNvidiaVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetAmdVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetAmdDx11VidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetIntelVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetIntelQsvDx11VidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetIntelQsvVaapiVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetVaapiVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetIntelVaapiFullVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetAmdVaapiFullVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetVaapiLimitedVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetAppleVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetAppleVidFiltersPreferred(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetRkmppVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetRkmppVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetVideoProcessingFilterParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetOverwriteColorPropertiesParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.Boolean)
GetInputHdrParam(System.String)
GetOutputSdrParam(System.String)
GetVideoColorBitDepth(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetHardwareVideoDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetHwDecoderName(MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String,System.String,System.Int32)
GetHwaccelType(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.Int32,System.Boolean)
GetQsvHwVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetNvdecVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetAmfVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetVaapiVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetVideotoolboxVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetRkmppVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetNumberOfThreads(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
TryStreamCopy(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetFfmpegAnalyzeDurationArg(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetFfmpegProbesizeArg()
GetInputModifier(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
AttachMediaSourceInfo(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Dto.MediaSourceInfo,System.String)
ShiftAudioCodecsIfNeeded(System.Collections.Generic.List`1<System.String>,MediaBrowser.Model.Entities.MediaStream)
ShiftVideoCodecsIfNeeded(System.Collections.Generic.List`1<System.String>,MediaBrowser.Model.Configuration.EncodingOptions)
NormalizeSubtitleEmbed(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetSubtitleEmbedArguments(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetProgressiveVideoFullCommandLine(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.EncoderPreset)
GetOutputFFlags(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetProgressiveVideoArguments(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,MediaBrowser.Model.Entities.EncoderPreset)
GetProgressiveVideoAudioArguments(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetProgressiveAudioFullCommandLine(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
FindIndex(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Model.Entities.MediaStream>,MediaBrowser.Model.Entities.MediaStream)
IsCopyCodec(System.String)
ShouldEncodeSubtitle(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
NeedsExternalSubtitleMuxing(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetVideoSyncOption(System.String,System.Version)