< 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: 3604
Coverable lines: 3787
Total lines: 7986
Line coverage: 4.8%
Branch coverage
2%
Covered branches: 112
Total branches: 3747
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/9/2026 - 12:13: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: 79676/18/2026 - 12:16:24 AM Line coverage: 4.8% (183/3787) Branch coverage: 2.9% (112/3747) Total lines: 7986 3/9/2026 - 12:13: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: 79676/18/2026 - 12:16:24 AM Line coverage: 4.8% (183/3787) Branch coverage: 2.9% (112/3747) Total lines: 7986

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(...)100%210%
CanStreamCopyAudio(...)0%2040%
GetAudioStreamCopyFailureReasons(...)0%1056320%
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%812280%
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 MediaBrowser.Model.Session;
 29using Microsoft.Extensions.Configuration;
 30using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
 31
 32namespace MediaBrowser.Controller.MediaEncoding
 33{
 34    public partial class EncodingHelper
 35    {
 36        /// <summary>
 37        /// The codec validation regex string.
 38        /// This regular expression matches strings that consist of alphanumeric characters, hyphens,
 39        /// periods, underscores, commas, and vertical bars, with a length between 0 and 40 characters.
 40        /// This should matches all common valid codecs.
 41        /// </summary>
 42        public const string ContainerValidationRegexStr = @"^[a-zA-Z0-9\-\._,|]{0,40}$";
 43
 44        /// <summary>
 45        /// The level validation regex string.
 46        /// This regular expression matches strings representing a double.
 47        /// </summary>
 48        public const string LevelValidationRegexStr = @"-?[0-9]+(?:\.[0-9]+)?";
 49
 50        private const string _defaultMjpegEncoder = "mjpeg";
 51
 52        private const string QsvAlias = "qs";
 53        private const string VaapiAlias = "va";
 54        private const string D3d11vaAlias = "dx11";
 55        private const string VideotoolboxAlias = "vt";
 56        private const string RkmppAlias = "rk";
 57        private const string OpenclAlias = "ocl";
 58        private const string CudaAlias = "cu";
 59        private const string DrmAlias = "dr";
 60        private const string VulkanAlias = "vk";
 61        private readonly IApplicationPaths _appPaths;
 62        private readonly IMediaEncoder _mediaEncoder;
 63        private readonly ISubtitleEncoder _subtitleEncoder;
 64        private readonly IConfiguration _config;
 65        private readonly IConfigurationManager _configurationManager;
 66        private readonly IPathManager _pathManager;
 67
 68        // i915 hang was fixed by linux 6.2 (3f882f2)
 4469        private readonly Version _minKerneli915Hang = new Version(5, 18);
 4470        private readonly Version _maxKerneli915Hang = new Version(6, 1, 3);
 4471        private readonly Version _minFixedKernel60i915Hang = new Version(6, 0, 18);
 4472        private readonly Version _minKernelVersionAmdVkFmtModifier = new Version(5, 15);
 73
 4474        private readonly Version _minFFmpegImplicitHwaccel = new Version(6, 0);
 4475        private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0);
 4476        private readonly Version _minFFmpegOclCuTonemapMode = new Version(5, 1, 3);
 4477        private readonly Version _minFFmpegSvtAv1Params = new Version(5, 1);
 4478        private readonly Version _minFFmpegVaapiH26xEncA53CcSei = new Version(6, 0);
 4479        private readonly Version _minFFmpegReadrateOption = new Version(5, 0);
 4480        private readonly Version _minFFmpegWorkingVtHwSurface = new Version(7, 0, 1);
 4481        private readonly Version _minFFmpegDisplayRotationOption = new Version(6, 0);
 4482        private readonly Version _minFFmpegAdvancedTonemapMode = new Version(7, 0, 1);
 4483        private readonly Version _minFFmpegAlteredVaVkInterop = new Version(7, 0, 1);
 4484        private readonly Version _minFFmpegQsvVppTonemapOption = new Version(7, 0, 1);
 4485        private readonly Version _minFFmpegQsvVppOutRangeOption = new Version(7, 0, 1);
 4486        private readonly Version _minFFmpegVaapiDeviceVendorId = new Version(7, 0, 1);
 4487        private readonly Version _minFFmpegQsvVppScaleModeOption = new Version(6, 0);
 4488        private readonly Version _minFFmpegRkmppHevcDecDoviRpu = new Version(7, 1, 1);
 4489        private readonly Version _minFFmpegReadrateCatchupOption = new Version(8, 0);
 4490        private readonly Version _minFFmpegNoiseBsfDrop = new Version(5, 0);
 91
 092        private static readonly string[] _videoProfilesH264 =
 093        [
 094            "ConstrainedBaseline",
 095            "Baseline",
 096            "Extended",
 097            "Main",
 098            "High",
 099            "ProgressiveHigh",
 0100            "ConstrainedHigh",
 0101            "High10"
 0102        ];
 103
 0104        private static readonly string[] _videoProfilesH265 =
 0105        [
 0106            "Main",
 0107            "Main10"
 0108        ];
 109
 0110        private static readonly string[] _videoProfilesAv1 =
 0111        [
 0112            "Main",
 0113            "High",
 0114            "Professional",
 0115        ];
 116
 0117        private static readonly HashSet<string> _mp4ContainerNames = new(StringComparer.OrdinalIgnoreCase)
 0118        {
 0119            "mp4",
 0120            "m4a",
 0121            "m4p",
 0122            "m4b",
 0123            "m4r",
 0124            "m4v",
 0125        };
 126
 0127        private static readonly TonemappingMode[] _legacyTonemapModes = [TonemappingMode.max, TonemappingMode.rgb];
 0128        private static readonly TonemappingMode[] _advancedTonemapModes = [TonemappingMode.lum, TonemappingMode.itp];
 129
 130        // Set max transcoding channels for encoders that can't handle more than a set amount of channels
 131        // AAC, FLAC, ALAC, libopus, libvorbis encoders all support at least 8 channels
 0132        private static readonly Dictionary<string, int> _audioTranscodeChannelLookup = new(StringComparer.OrdinalIgnoreC
 0133        {
 0134            { "libmp3lame", 2 },
 0135            { "libfdk_aac", 6 },
 0136            { "ac3", 6 },
 0137            { "eac3", 6 },
 0138            { "dca", 6 },
 0139            { "mlp", 6 },
 0140            { "truehd", 6 },
 0141        };
 142
 0143        private static readonly Dictionary<HardwareAccelerationType, string> _mjpegCodecMap = new()
 0144        {
 0145            { HardwareAccelerationType.vaapi, _defaultMjpegEncoder + "_vaapi" },
 0146            { HardwareAccelerationType.qsv, _defaultMjpegEncoder + "_qsv" },
 0147            { HardwareAccelerationType.videotoolbox, _defaultMjpegEncoder + "_videotoolbox" },
 0148            { HardwareAccelerationType.rkmpp, _defaultMjpegEncoder + "_rkmpp" }
 0149        };
 150
 0151        public static readonly string[] LosslessAudioCodecs =
 0152        [
 0153            "alac",
 0154            "ape",
 0155            "flac",
 0156            "mlp",
 0157            "truehd",
 0158            "wavpack"
 0159        ];
 160
 161        public EncodingHelper(
 162            IApplicationPaths appPaths,
 163            IMediaEncoder mediaEncoder,
 164            ISubtitleEncoder subtitleEncoder,
 165            IConfiguration config,
 166            IConfigurationManager configurationManager,
 167            IPathManager pathManager)
 168        {
 44169            _appPaths = appPaths;
 44170            _mediaEncoder = mediaEncoder;
 44171            _subtitleEncoder = subtitleEncoder;
 44172            _config = config;
 44173            _configurationManager = configurationManager;
 44174            _pathManager = pathManager;
 44175        }
 176
 177        private enum DynamicHdrMetadataRemovalPlan
 178        {
 179            None,
 180            RemoveDovi,
 181            RemoveHdr10Plus,
 182        }
 183
 184        /// <summary>
 185        /// The codec validation regex.
 186        /// This regular expression matches strings that consist of alphanumeric characters, hyphens,
 187        /// periods, underscores, commas, and vertical bars, with a length between 0 and 40 characters.
 188        /// This should matches all common valid codecs.
 189        /// </summary>
 190        [GeneratedRegex(ContainerValidationRegexStr)]
 191        public static partial Regex ContainerValidationRegex();
 192
 193        /// <summary>
 194        /// The level validation regex string.
 195        /// This regular expression matches strings representing a double.
 196        /// </summary>
 197        [GeneratedRegex(LevelValidationRegexStr)]
 198        public static partial Regex LevelValidationRegex();
 199
 200        [GeneratedRegex(@"\s+")]
 201        private static partial Regex WhiteSpaceRegex();
 202
 203        public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0204            => GetH26xOrAv1Encoder("libx264", "h264", state, encodingOptions);
 205
 206        public string GetH265Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0207            => GetH26xOrAv1Encoder("libx265", "hevc", state, encodingOptions);
 208
 209        public string GetAv1Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0210            => GetH26xOrAv1Encoder("libsvtav1", "av1", state, encodingOptions);
 211
 212        private string GetH26xOrAv1Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptio
 213        {
 214            // Only use alternative encoders for video files.
 215            // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying
 216            // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such 
 0217            if (state.VideoType == VideoType.VideoFile)
 218            {
 0219                var hwType = encodingOptions.HardwareAccelerationType;
 220
 0221                var codecMap = new Dictionary<HardwareAccelerationType, string>()
 0222                {
 0223                    { HardwareAccelerationType.amf,                  hwEncoder + "_amf" },
 0224                    { HardwareAccelerationType.nvenc,                hwEncoder + "_nvenc" },
 0225                    { HardwareAccelerationType.qsv,                  hwEncoder + "_qsv" },
 0226                    { HardwareAccelerationType.vaapi,                hwEncoder + "_vaapi" },
 0227                    { HardwareAccelerationType.videotoolbox,         hwEncoder + "_videotoolbox" },
 0228                    { HardwareAccelerationType.v4l2m2m,              hwEncoder + "_v4l2m2m" },
 0229                    { HardwareAccelerationType.rkmpp,                hwEncoder + "_rkmpp" },
 0230                };
 231
 0232                if (hwType != HardwareAccelerationType.none
 0233                    && encodingOptions.EnableHardwareEncoding
 0234                    && codecMap.TryGetValue(hwType, out var preferredEncoder)
 0235                    && _mediaEncoder.SupportsEncoder(preferredEncoder))
 236                {
 0237                    return preferredEncoder;
 238                }
 239            }
 240
 0241            return defaultEncoder;
 242        }
 243
 244        private string GetMjpegEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 245        {
 0246            if (state.VideoType == VideoType.VideoFile)
 247            {
 0248                var hwType = encodingOptions.HardwareAccelerationType;
 249
 250                // Only enable VA-API MJPEG encoder on Intel iHD driver.
 251                // Legacy platforms supported ONLY by i965 do not support MJPEG encoder.
 0252                if (hwType == HardwareAccelerationType.vaapi
 0253                    && !_mediaEncoder.IsVaapiDeviceInteliHD)
 254                {
 0255                    return _defaultMjpegEncoder;
 256                }
 257
 0258                if (hwType != HardwareAccelerationType.none
 0259                    && encodingOptions.EnableHardwareEncoding
 0260                    && _mjpegCodecMap.TryGetValue(hwType, out var preferredEncoder)
 0261                    && _mediaEncoder.SupportsEncoder(preferredEncoder))
 262                {
 0263                    return preferredEncoder;
 264                }
 265            }
 266
 0267            return _defaultMjpegEncoder;
 268        }
 269
 270        private bool IsVaapiSupported(EncodingJobInfo state)
 271        {
 272            // vaapi will throw an error with this input
 273            // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99.
 0274            if (string.Equals(state.VideoStream?.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 275            {
 0276                return false;
 277            }
 278
 0279            return _mediaEncoder.SupportsHwaccel("vaapi");
 280        }
 281
 282        private bool IsVaapiFullSupported()
 283        {
 0284            return _mediaEncoder.SupportsHwaccel("drm")
 0285                   && _mediaEncoder.SupportsHwaccel("vaapi")
 0286                   && _mediaEncoder.SupportsFilter("scale_vaapi")
 0287                   && _mediaEncoder.SupportsFilter("deinterlace_vaapi")
 0288                   && _mediaEncoder.SupportsFilter("tonemap_vaapi")
 0289                   && _mediaEncoder.SupportsFilter("procamp_vaapi")
 0290                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVaapiFrameSync)
 0291                   && _mediaEncoder.SupportsFilter("transpose_vaapi")
 0292                   && _mediaEncoder.SupportsFilter("hwupload_vaapi");
 293        }
 294
 295        private bool IsRkmppFullSupported()
 296        {
 0297            return _mediaEncoder.SupportsHwaccel("rkmpp")
 0298                   && _mediaEncoder.SupportsFilter("scale_rkrga")
 0299                   && _mediaEncoder.SupportsFilter("vpp_rkrga")
 0300                   && _mediaEncoder.SupportsFilter("overlay_rkrga");
 301        }
 302
 303        private bool IsOpenclFullSupported()
 304        {
 0305            return _mediaEncoder.SupportsHwaccel("opencl")
 0306                   && _mediaEncoder.SupportsFilter("scale_opencl")
 0307                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390)
 0308                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclFrameSync);
 309
 310            // Let transpose_opencl optional for the time being.
 311        }
 312
 313        private bool IsCudaFullSupported()
 314        {
 0315            return _mediaEncoder.SupportsHwaccel("cuda")
 0316                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat)
 0317                   && _mediaEncoder.SupportsFilter("yadif_cuda")
 0318                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName)
 0319                   && _mediaEncoder.SupportsFilter("overlay_cuda")
 0320                   && _mediaEncoder.SupportsFilter("hwupload_cuda");
 321
 322            // Let transpose_cuda optional for the time being.
 323        }
 324
 325        private bool IsVulkanFullSupported()
 326        {
 0327            return _mediaEncoder.SupportsHwaccel("vulkan")
 0328                   && _mediaEncoder.SupportsFilter("libplacebo")
 0329                   && _mediaEncoder.SupportsFilter("scale_vulkan")
 0330                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVulkanFrameSync)
 0331                   && _mediaEncoder.SupportsFilter("transpose_vulkan")
 0332                   && _mediaEncoder.SupportsFilter("flip_vulkan");
 333        }
 334
 335        private bool IsVideoToolboxFullSupported()
 336        {
 0337            return _mediaEncoder.SupportsHwaccel("videotoolbox")
 0338                && _mediaEncoder.SupportsFilter("yadif_videotoolbox")
 0339                && _mediaEncoder.SupportsFilter("overlay_videotoolbox")
 0340                && _mediaEncoder.SupportsFilter("tonemap_videotoolbox")
 0341                && _mediaEncoder.SupportsFilter("scale_vt");
 342
 343            // Let transpose_vt optional for the time being.
 344        }
 345
 346        private bool IsSwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 347        {
 0348            if (state.VideoStream is null
 0349                || GetVideoColorBitDepth(state) < 10
 0350                || !_mediaEncoder.SupportsFilter("tonemapx"))
 351            {
 0352                return false;
 353            }
 354
 0355            return state.VideoStream.VideoRange == VideoRange.HDR;
 356        }
 357
 358        private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 359        {
 0360            if (state.VideoStream is null
 0361                || !options.EnableTonemapping
 0362                || GetVideoColorBitDepth(state) < 10)
 363            {
 0364                return false;
 365            }
 366
 0367            if (state.VideoStream.VideoRange == VideoRange.HDR
 0368                && state.VideoStream.VideoRangeType == VideoRangeType.DOVI)
 369            {
 370                // Only native SW decoder, HW accelerator and hevc_rkmpp decoder can parse dovi rpu.
 0371                var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 372
 0373                var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 0374                if (isRkmppDecoder
 0375                    && _mediaEncoder.EncoderVersion >= _minFFmpegRkmppHevcDecDoviRpu
 0376                    && string.Equals(state.VideoStream?.Codec, "hevc", StringComparison.OrdinalIgnoreCase))
 377                {
 0378                    return true;
 379                }
 380
 0381                var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 0382                var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 0383                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 0384                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 0385                var isVideoToolBoxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 0386                return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder || isVideoToolBoxDecoder;
 387            }
 388
 389            // GPU tonemapping supports all HDR RangeTypes
 0390            return state.VideoStream.VideoRange == VideoRange.HDR;
 391        }
 392
 393        private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 394        {
 0395            if (state.VideoStream is null)
 396            {
 0397                return false;
 398            }
 399
 400            // libplacebo has partial Dolby Vision to SDR tonemapping support.
 0401            return options.EnableTonemapping
 0402                   && state.VideoStream.VideoRange == VideoRange.HDR
 0403                   && GetVideoColorBitDepth(state) == 10;
 404        }
 405
 406        private bool IsIntelVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 407        {
 0408            if (state.VideoStream is null
 0409                || !options.EnableVppTonemapping
 0410                || GetVideoColorBitDepth(state) < 10)
 411            {
 0412                return false;
 413            }
 414
 415            // prefer 'tonemap_vaapi' over 'vpp_qsv' on Linux for supporting Gen9/KBLx.
 416            // 'vpp_qsv' requires VPL, which is only supported on Gen12/TGLx and newer.
 0417            if (OperatingSystem.IsWindows()
 0418                && options.HardwareAccelerationType == HardwareAccelerationType.qsv
 0419                && _mediaEncoder.EncoderVersion < _minFFmpegQsvVppTonemapOption)
 420            {
 0421                return false;
 422            }
 423
 0424            return state.VideoStream.VideoRange == VideoRange.HDR
 0425                   && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
 0426                       || IsHdr10Plus(state.VideoStream)
 0427                       || IsDoviWithHdr10Bl(state.VideoStream));
 428        }
 429
 430        private bool IsVideoToolboxTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 431        {
 0432            if (state.VideoStream is null
 0433                || !options.EnableVideoToolboxTonemapping
 0434                || GetVideoColorBitDepth(state) < 10)
 435            {
 0436                return false;
 437            }
 438
 439            // Certain DV profile 5 video works in Safari with direct playing, but the VideoToolBox does not produce cor
 440            // All other HDR formats working.
 0441            return state.VideoStream.VideoRange == VideoRange.HDR
 0442                   && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
 0443                       || IsHdr10Plus(state.VideoStream)
 0444                       || IsDoviWithHdr10Bl(state.VideoStream)
 0445                       || state.VideoStream.VideoRangeType == VideoRangeType.HLG);
 446        }
 447
 448        private static bool IsDeinterlaceAvailable(EncodingJobInfo state)
 449        {
 0450            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 0451            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 0452            return doDeintH264 || doDeintHevc;
 453        }
 454
 455        private bool IsVideoStreamHevcRext(EncodingJobInfo state)
 456        {
 0457            var videoStream = state.VideoStream;
 0458            if (videoStream is null)
 459            {
 0460                return false;
 461            }
 462
 0463            return string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 0464                   && (string.Equals(videoStream.Profile, "Rext", StringComparison.OrdinalIgnoreCase)
 0465                       || string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 0466                       || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 0467                       || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 0468                       || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 0469                       || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase)
 0470                       || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)
 0471                       || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase));
 472        }
 473
 474        /// <summary>
 475        /// Gets the name of the output video codec.
 476        /// </summary>
 477        /// <param name="state">Encoding state.</param>
 478        /// <param name="encodingOptions">Encoding options.</param>
 479        /// <returns>Encoder string.</returns>
 480        public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 481        {
 4482            var codec = state.OutputVideoCodec;
 483
 4484            if (!string.IsNullOrEmpty(codec))
 485            {
 0486                if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 487                {
 0488                    return GetAv1Encoder(state, encodingOptions);
 489                }
 490
 0491                if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 0492                    || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
 493                {
 0494                    return GetH265Encoder(state, encodingOptions);
 495                }
 496
 0497                if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
 498                {
 0499                    return GetH264Encoder(state, encodingOptions);
 500                }
 501
 0502                if (string.Equals(codec, "mjpeg", StringComparison.OrdinalIgnoreCase))
 503                {
 0504                    return GetMjpegEncoder(state, encodingOptions);
 505                }
 506
 0507                if (ContainerValidationRegex().IsMatch(codec))
 508                {
 0509                    return codec.ToLowerInvariant();
 510                }
 511            }
 512
 4513            return "copy";
 514        }
 515
 516        /// <summary>
 517        /// Gets the user agent param.
 518        /// </summary>
 519        /// <param name="state">The state.</param>
 520        /// <returns>System.String.</returns>
 521        public string GetUserAgentParam(EncodingJobInfo state)
 522        {
 0523            if (state.RemoteHttpHeaders.TryGetValue("User-Agent", out string useragent))
 524            {
 0525                return "-user_agent \"" + useragent + "\"";
 526            }
 527
 0528            return string.Empty;
 529        }
 530
 531        /// <summary>
 532        /// Gets the referer param.
 533        /// </summary>
 534        /// <param name="state">The state.</param>
 535        /// <returns>System.String.</returns>
 536        public string GetRefererParam(EncodingJobInfo state)
 537        {
 0538            if (state.RemoteHttpHeaders.TryGetValue("Referer", out string referer))
 539            {
 0540                return "-referer \"" + referer + "\"";
 541            }
 542
 0543            return string.Empty;
 544        }
 545
 546        public static string GetInputFormat(string container)
 547        {
 0548            if (string.IsNullOrEmpty(container) || !ContainerValidationRegex().IsMatch(container))
 549            {
 0550                return null;
 551            }
 552
 0553            container = container.Replace("mkv", "matroska", StringComparison.OrdinalIgnoreCase);
 554
 0555            if (string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
 556            {
 0557                return "mpegts";
 558            }
 559
 560            // For these need to find out the ffmpeg names
 0561            if (string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase))
 562            {
 0563                return null;
 564            }
 565
 0566            if (string.Equals(container, "wmv", StringComparison.OrdinalIgnoreCase))
 567            {
 0568                return null;
 569            }
 570
 0571            if (string.Equals(container, "mts", StringComparison.OrdinalIgnoreCase))
 572            {
 0573                return null;
 574            }
 575
 0576            if (string.Equals(container, "vob", StringComparison.OrdinalIgnoreCase))
 577            {
 0578                return null;
 579            }
 580
 0581            if (string.Equals(container, "mpg", StringComparison.OrdinalIgnoreCase))
 582            {
 0583                return null;
 584            }
 585
 0586            if (string.Equals(container, "mpeg", StringComparison.OrdinalIgnoreCase))
 587            {
 0588                return null;
 589            }
 590
 0591            if (string.Equals(container, "rec", StringComparison.OrdinalIgnoreCase))
 592            {
 0593                return null;
 594            }
 595
 0596            if (string.Equals(container, "dvr-ms", StringComparison.OrdinalIgnoreCase))
 597            {
 0598                return null;
 599            }
 600
 0601            if (string.Equals(container, "ogm", StringComparison.OrdinalIgnoreCase))
 602            {
 0603                return null;
 604            }
 605
 0606            if (string.Equals(container, "divx", StringComparison.OrdinalIgnoreCase))
 607            {
 0608                return null;
 609            }
 610
 0611            if (string.Equals(container, "tp", StringComparison.OrdinalIgnoreCase))
 612            {
 0613                return null;
 614            }
 615
 0616            if (string.Equals(container, "rmvb", StringComparison.OrdinalIgnoreCase))
 617            {
 0618                return null;
 619            }
 620
 0621            if (string.Equals(container, "rtp", StringComparison.OrdinalIgnoreCase))
 622            {
 0623                return null;
 624            }
 625
 626            // Seeing reported failures here, not sure yet if this is related to specifying input format
 0627            if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase))
 628            {
 0629                return null;
 630            }
 631
 632            // obviously don't do this for strm files
 0633            if (string.Equals(container, "strm", StringComparison.OrdinalIgnoreCase))
 634            {
 0635                return null;
 636            }
 637
 638            // ISO files don't have an ffmpeg format
 0639            if (string.Equals(container, "iso", StringComparison.OrdinalIgnoreCase))
 640            {
 0641                return null;
 642            }
 643
 0644            return container;
 645        }
 646
 647        /// <summary>
 648        /// Gets decoder from a codec.
 649        /// </summary>
 650        /// <param name="codec">Codec to use.</param>
 651        /// <returns>Decoder string.</returns>
 652        public string GetDecoderFromCodec(string codec)
 653        {
 654            // For these need to find out the ffmpeg names
 0655            if (string.Equals(codec, "mp2", StringComparison.OrdinalIgnoreCase))
 656            {
 0657                return null;
 658            }
 659
 0660            if (string.Equals(codec, "aac_latm", StringComparison.OrdinalIgnoreCase))
 661            {
 0662                return null;
 663            }
 664
 0665            if (string.Equals(codec, "eac3", StringComparison.OrdinalIgnoreCase))
 666            {
 0667                return null;
 668            }
 669
 0670            if (_mediaEncoder.SupportsDecoder(codec))
 671            {
 0672                return codec;
 673            }
 674
 0675            return null;
 676        }
 677
 678        /// <summary>
 679        /// Infers the audio codec based on the url.
 680        /// </summary>
 681        /// <param name="container">Container to use.</param>
 682        /// <returns>Codec string.</returns>
 683        public string InferAudioCodec(string container)
 684        {
 0685            if (string.IsNullOrWhiteSpace(container))
 686            {
 687                // this may not work, but if the client is that broken we cannot do anything better
 0688                return "aac";
 689            }
 690
 0691            var inferredCodec = container.ToLowerInvariant();
 692
 0693            return inferredCodec switch
 0694            {
 0695                "ogg" or "oga" or "ogv" or "webm" or "webma" => "opus",
 0696                "m4a" or "m4b" or "mp4" or "mov" or "mkv" or "mka" => "aac",
 0697                "ts" or "avi" or "flv" or "f4v" or "swf" => "mp3",
 0698                _ => inferredCodec
 0699            };
 700        }
 701
 702        /// <summary>
 703        /// Infers the video codec.
 704        /// </summary>
 705        /// <param name="url">The URL.</param>
 706        /// <returns>System.Nullable{VideoCodecs}.</returns>
 707        public string InferVideoCodec(string url)
 708        {
 0709            var ext = Path.GetExtension(url.AsSpan());
 710
 0711            if (ext.Equals(".asf", StringComparison.OrdinalIgnoreCase))
 712            {
 0713                return "wmv";
 714            }
 715
 0716            if (ext.Equals(".webm", StringComparison.OrdinalIgnoreCase))
 717            {
 718                // TODO: this may not always mean VP8, as the codec ages
 0719                return "vp8";
 720            }
 721
 0722            if (ext.Equals(".ogg", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ogv", StringComparison.OrdinalIgn
 723            {
 0724                return "theora";
 725            }
 726
 0727            if (ext.Equals(".m3u8", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ts", StringComparison.OrdinalIgn
 728            {
 0729                return "h264";
 730            }
 731
 0732            return "copy";
 733        }
 734
 735        public int GetVideoProfileScore(string videoCodec, string videoProfile)
 736        {
 737            // strip spaces because they may be stripped out on the query string
 0738            string profile = videoProfile.Replace(" ", string.Empty, StringComparison.Ordinal);
 0739            if (string.Equals("h264", videoCodec, StringComparison.OrdinalIgnoreCase))
 740            {
 0741                return Array.FindIndex(_videoProfilesH264, x => string.Equals(x, profile, StringComparison.OrdinalIgnore
 742            }
 743
 0744            if (string.Equals("hevc", videoCodec, StringComparison.OrdinalIgnoreCase))
 745            {
 0746                return Array.FindIndex(_videoProfilesH265, x => string.Equals(x, profile, StringComparison.OrdinalIgnore
 747            }
 748
 0749            if (string.Equals("av1", videoCodec, StringComparison.OrdinalIgnoreCase))
 750            {
 0751                return Array.FindIndex(_videoProfilesAv1, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreC
 752            }
 753
 0754            return -1;
 755        }
 756
 757        /// <summary>
 758        /// Gets the audio encoder.
 759        /// </summary>
 760        /// <param name="state">The state.</param>
 761        /// <returns>System.String.</returns>
 762        public string GetAudioEncoder(EncodingJobInfo state)
 763        {
 0764            var codec = state.OutputAudioCodec;
 765
 0766            if (!ContainerValidationRegex().IsMatch(codec))
 767            {
 0768                codec = "aac";
 769            }
 770
 0771            if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
 772            {
 773                // Use Apple's aac encoder if available as it provides best audio quality
 0774                if (_mediaEncoder.SupportsEncoder("aac_at"))
 775                {
 0776                    return "aac_at";
 777                }
 778
 779                // Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
 0780                if (_mediaEncoder.SupportsEncoder("libfdk_aac"))
 781                {
 0782                    return "libfdk_aac";
 783                }
 784
 0785                return "aac";
 786            }
 787
 0788            if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
 789            {
 0790                return "libmp3lame";
 791            }
 792
 0793            if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase))
 794            {
 0795                return "libvorbis";
 796            }
 797
 0798            if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
 799            {
 0800                return "libopus";
 801            }
 802
 0803            if (string.Equals(codec, "flac", StringComparison.OrdinalIgnoreCase))
 804            {
 0805                return "flac";
 806            }
 807
 0808            if (string.Equals(codec, "dts", StringComparison.OrdinalIgnoreCase))
 809            {
 0810                return "dca";
 811            }
 812
 0813            if (string.Equals(codec, "alac", StringComparison.OrdinalIgnoreCase))
 814            {
 815                // The ffmpeg upstream breaks the AudioToolbox ALAC encoder in version 6.1 but fixes it in version 7.0.
 816                // Since ALAC is lossless in quality and the AudioToolbox encoder is not faster,
 817                // its only benefit is a smaller file size.
 818                // To prevent problems, use the ffmpeg native encoder instead.
 0819                return "alac";
 820            }
 821
 0822            return codec.ToLowerInvariant();
 823        }
 824
 825        private string GetRkmppDeviceArgs(string alias)
 826        {
 0827            alias ??= RkmppAlias;
 828
 829            // device selection in rk is not supported.
 0830            return " -init_hw_device rkmpp=" + alias;
 831        }
 832
 833        private string GetVideoToolboxDeviceArgs(string alias)
 834        {
 0835            alias ??= VideotoolboxAlias;
 836
 837            // device selection in vt is not supported.
 0838            return " -init_hw_device videotoolbox=" + alias;
 839        }
 840
 841        private string GetCudaDeviceArgs(int deviceIndex, string alias)
 842        {
 0843            alias ??= CudaAlias;
 0844            deviceIndex = deviceIndex >= 0
 0845                ? deviceIndex
 0846                : 0;
 847
 0848            return string.Format(
 0849                CultureInfo.InvariantCulture,
 0850                " -init_hw_device cuda={0}:{1}",
 0851                alias,
 0852                deviceIndex);
 853        }
 854
 855        private string GetVulkanDeviceArgs(int deviceIndex, string deviceName, string srcDeviceAlias, string alias)
 856        {
 0857            alias ??= VulkanAlias;
 0858            deviceIndex = deviceIndex >= 0
 0859                ? deviceIndex
 0860                : 0;
 0861            var vendorOpts = string.IsNullOrEmpty(deviceName)
 0862                ? ":" + deviceIndex
 0863                : ":" + "\"" + deviceName + "\"";
 0864            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0865                ? vendorOpts
 0866                : "@" + srcDeviceAlias;
 867
 0868            return string.Format(
 0869                CultureInfo.InvariantCulture,
 0870                " -init_hw_device vulkan={0}{1}",
 0871                alias,
 0872                options);
 873        }
 874
 875        private string GetOpenclDeviceArgs(int deviceIndex, string deviceVendorName, string srcDeviceAlias, string alias
 876        {
 0877            alias ??= OpenclAlias;
 0878            deviceIndex = deviceIndex >= 0
 0879                ? deviceIndex
 0880                : 0;
 0881            var vendorOpts = string.IsNullOrEmpty(deviceVendorName)
 0882                ? ":0.0"
 0883                : ":." + deviceIndex + ",device_vendor=\"" + deviceVendorName + "\"";
 0884            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0885                ? vendorOpts
 0886                : "@" + srcDeviceAlias;
 887
 0888            return string.Format(
 0889                CultureInfo.InvariantCulture,
 0890                " -init_hw_device opencl={0}{1}",
 0891                alias,
 0892                options);
 893        }
 894
 895        private string GetD3d11vaDeviceArgs(int deviceIndex, string deviceVendorId, string alias)
 896        {
 0897            alias ??= D3d11vaAlias;
 0898            deviceIndex = deviceIndex >= 0 ? deviceIndex : 0;
 0899            var options = string.IsNullOrEmpty(deviceVendorId)
 0900                ? deviceIndex.ToString(CultureInfo.InvariantCulture)
 0901                : ",vendor=" + deviceVendorId;
 902
 0903            return string.Format(
 0904                CultureInfo.InvariantCulture,
 0905                " -init_hw_device d3d11va={0}:{1}",
 0906                alias,
 0907                options);
 908        }
 909
 910        private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string vendorId, st
 911        {
 0912            alias ??= VaapiAlias;
 0913            var haveVendorId = !string.IsNullOrEmpty(vendorId)
 0914                && _mediaEncoder.EncoderVersion >= _minFFmpegVaapiDeviceVendorId;
 915
 916            // Priority: 'renderNodePath' > 'vendorId' > 'kernelDriver'
 0917            var driverOpts = File.Exists(renderNodePath)
 0918                ? renderNodePath
 0919                : (haveVendorId ? $",vendor_id={vendorId}" : (string.IsNullOrEmpty(kernelDriver) ? string.Empty : $",ker
 920
 921            // 'driver' behaves similarly to env LIBVA_DRIVER_NAME
 0922            driverOpts += string.IsNullOrEmpty(driver) ? string.Empty : ",driver=" + driver;
 923
 0924            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0925                ? (string.IsNullOrEmpty(driverOpts) ? string.Empty : ":" + driverOpts)
 0926                : "@" + srcDeviceAlias;
 927
 0928            return string.Format(
 0929                CultureInfo.InvariantCulture,
 0930                " -init_hw_device vaapi={0}{1}",
 0931                alias,
 0932                options);
 933        }
 934
 935        private string GetDrmDeviceArgs(string renderNodePath, string alias)
 936        {
 0937            alias ??= DrmAlias;
 0938            renderNodePath = renderNodePath ?? "/dev/dri/renderD128";
 939
 0940            return string.Format(
 0941                CultureInfo.InvariantCulture,
 0942                " -init_hw_device drm={0}:{1}",
 0943                alias,
 0944                renderNodePath);
 945        }
 946
 947        private string GetQsvDeviceArgs(string renderNodePath, string alias)
 948        {
 0949            var arg = " -init_hw_device qsv=" + (alias ?? QsvAlias);
 0950            if (OperatingSystem.IsLinux())
 951            {
 952                // derive qsv from vaapi device
 0953                return GetVaapiDeviceArgs(renderNodePath, "iHD", "i915", "0x8086", null, VaapiAlias) + arg + "@" + Vaapi
 954            }
 955
 0956            if (OperatingSystem.IsWindows())
 957            {
 958                // on Windows, the deviceIndex is an int
 0959                if (int.TryParse(renderNodePath, NumberStyles.Integer, CultureInfo.InvariantCulture, out int deviceIndex
 960                {
 0961                    return GetD3d11vaDeviceArgs(deviceIndex, string.Empty, D3d11vaAlias) + arg + "@" + D3d11vaAlias;
 962                }
 963
 964                // derive qsv from d3d11va device
 0965                return GetD3d11vaDeviceArgs(0, "0x8086", D3d11vaAlias) + arg + "@" + D3d11vaAlias;
 966            }
 967
 0968            return null;
 969        }
 970
 971        private string GetFilterHwDeviceArgs(string alias)
 972        {
 0973            return string.IsNullOrEmpty(alias)
 0974                ? string.Empty
 0975                : " -filter_hw_device " + alias;
 976        }
 977
 978        public string GetGraphicalSubCanvasSize(EncodingJobInfo state)
 979        {
 980            // DVBSUB uses the fixed canvas size 720x576
 4981            if (state.SubtitleStream is not null
 4982                && ShouldEncodeSubtitle(state)
 4983                && !state.SubtitleStream.IsTextSubtitleStream
 4984                && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 985            {
 2986                var subtitleWidth = state.SubtitleStream?.Width;
 2987                var subtitleHeight = state.SubtitleStream?.Height;
 988
 2989                if (subtitleWidth.HasValue
 2990                    && subtitleHeight.HasValue
 2991                    && subtitleWidth.Value > 0
 2992                    && subtitleHeight.Value > 0)
 993                {
 0994                    return string.Format(
 0995                        CultureInfo.InvariantCulture,
 0996                        " -canvas_size {0}x{1}",
 0997                        subtitleWidth.Value,
 0998                        subtitleHeight.Value);
 999                }
 1000            }
 1001
 41002            return string.Empty;
 1003        }
 1004
 1005        /// <summary>
 1006        /// Gets the input video hwaccel argument.
 1007        /// </summary>
 1008        /// <param name="state">Encoding state.</param>
 1009        /// <param name="options">Encoding options.</param>
 1010        /// <returns>Input video hwaccel arguments.</returns>
 1011        public string GetInputVideoHwaccelArgs(EncodingJobInfo state, EncodingOptions options)
 1012        {
 41013            if (!state.IsVideoRequest)
 1014            {
 01015                return string.Empty;
 1016            }
 1017
 41018            var vidEncoder = GetVideoEncoder(state, options) ?? string.Empty;
 41019            if (IsCopyCodec(vidEncoder))
 1020            {
 41021                return string.Empty;
 1022            }
 1023
 01024            var args = new StringBuilder();
 01025            var isWindows = OperatingSystem.IsWindows();
 01026            var isLinux = OperatingSystem.IsLinux();
 01027            var isMacOS = OperatingSystem.IsMacOS();
 01028            var optHwaccelType = options.HardwareAccelerationType;
 01029            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 01030            var isHwTonemapAvailable = IsHwTonemapAvailable(state, options);
 1031
 01032            if (optHwaccelType == HardwareAccelerationType.vaapi)
 1033            {
 01034                if (!isLinux || !_mediaEncoder.SupportsHwaccel("vaapi"))
 1035                {
 01036                    return string.Empty;
 1037                }
 1038
 01039                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01040                var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01041                if (!isVaapiDecoder && !isVaapiEncoder)
 1042                {
 01043                    return string.Empty;
 1044                }
 1045
 01046                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 1047                {
 01048                    args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "iHD", null, null, null, VaapiAlias));
 1049                }
 01050                else if (_mediaEncoder.IsVaapiDeviceInteli965)
 1051                {
 1052                    // Only override i965 since it has lower priority than iHD in libva lookup.
 01053                    Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME", "i965");
 01054                    Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME_JELLYFIN", "i965");
 01055                    args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "i965", null, null, null, VaapiAlias));
 1056                }
 1057
 01058                var filterDevArgs = string.Empty;
 01059                var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
 1060
 01061                if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 1062                {
 01063                    if (doOclTonemap && !isVaapiDecoder)
 1064                    {
 01065                        args.Append(GetOpenclDeviceArgs(0, null, VaapiAlias, OpenclAlias));
 01066                        filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1067                    }
 1068                }
 01069                else if (_mediaEncoder.IsVaapiDeviceAmd)
 1070                {
 1071                    // Disable AMD EFC feature since it's still unstable in upstream Mesa.
 01072                    Environment.SetEnvironmentVariable("AMD_DEBUG", "noefc");
 1073
 01074                    if (IsVulkanFullSupported()
 01075                        && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 01076                        && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 1077                    {
 01078                        args.Append(GetDrmDeviceArgs(options.VaapiDevice, DrmAlias));
 01079                        args.Append(GetVaapiDeviceArgs(null, null, null, null, DrmAlias, VaapiAlias));
 01080                        args.Append(GetVulkanDeviceArgs(0, null, DrmAlias, VulkanAlias));
 1081
 1082                        // libplacebo wants an explicitly set vulkan filter device.
 01083                        filterDevArgs = GetFilterHwDeviceArgs(VulkanAlias);
 1084                    }
 1085                    else
 1086                    {
 01087                        args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, null, null, VaapiAlias));
 01088                        filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias);
 1089
 01090                        if (doOclTonemap)
 1091                        {
 1092                            // ROCm/ROCr OpenCL runtime
 01093                            args.Append(GetOpenclDeviceArgs(0, "Advanced Micro Devices", null, OpenclAlias));
 01094                            filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1095                        }
 1096                    }
 1097                }
 01098                else if (doOclTonemap)
 1099                {
 01100                    args.Append(GetOpenclDeviceArgs(0, null, null, OpenclAlias));
 01101                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1102                }
 1103
 01104                args.Append(filterDevArgs);
 1105            }
 01106            else if (optHwaccelType == HardwareAccelerationType.qsv)
 1107            {
 01108                if ((!isLinux && !isWindows) || !_mediaEncoder.SupportsHwaccel("qsv"))
 1109                {
 01110                    return string.Empty;
 1111                }
 1112
 01113                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 01114                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01115                var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 01116                var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 01117                var isHwDecoder = isQsvDecoder || isVaapiDecoder || isD3d11vaDecoder;
 01118                if (!isHwDecoder && !isQsvEncoder)
 1119                {
 01120                    return string.Empty;
 1121                }
 1122
 01123                args.Append(GetQsvDeviceArgs(options.QsvDevice, QsvAlias));
 01124                var filterDevArgs = GetFilterHwDeviceArgs(QsvAlias);
 1125                // child device used by qsv.
 01126                if (_mediaEncoder.SupportsHwaccel("vaapi") || _mediaEncoder.SupportsHwaccel("d3d11va"))
 1127                {
 01128                    if (isHwTonemapAvailable && IsOpenclFullSupported())
 1129                    {
 01130                        var srcAlias = isLinux ? VaapiAlias : D3d11vaAlias;
 01131                        args.Append(GetOpenclDeviceArgs(0, null, srcAlias, OpenclAlias));
 01132                        if (!isHwDecoder)
 1133                        {
 01134                            filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1135                        }
 1136                    }
 1137                }
 1138
 01139                args.Append(filterDevArgs);
 1140            }
 01141            else if (optHwaccelType == HardwareAccelerationType.nvenc)
 1142            {
 01143                if ((!isLinux && !isWindows) || !IsCudaFullSupported())
 1144                {
 01145                    return string.Empty;
 1146                }
 1147
 01148                var isCuvidDecoder = vidDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase);
 01149                var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 01150                var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 01151                var isHwDecoder = isNvdecDecoder || isCuvidDecoder;
 01152                if (!isHwDecoder && !isNvencEncoder)
 1153                {
 01154                    return string.Empty;
 1155                }
 1156
 01157                args.Append(GetCudaDeviceArgs(0, CudaAlias))
 01158                     .Append(GetFilterHwDeviceArgs(CudaAlias));
 1159            }
 01160            else if (optHwaccelType == HardwareAccelerationType.amf)
 1161            {
 01162                if (!isWindows || !_mediaEncoder.SupportsHwaccel("d3d11va"))
 1163                {
 01164                    return string.Empty;
 1165                }
 1166
 01167                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 01168                var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 01169                if (!isD3d11vaDecoder && !isAmfEncoder)
 1170                {
 01171                    return string.Empty;
 1172                }
 1173
 1174                // no dxva video processor hw filter.
 01175                args.Append(GetD3d11vaDeviceArgs(0, "0x1002", D3d11vaAlias));
 01176                var filterDevArgs = string.Empty;
 01177                if (IsOpenclFullSupported())
 1178                {
 01179                    args.Append(GetOpenclDeviceArgs(0, null, D3d11vaAlias, OpenclAlias));
 01180                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1181                }
 1182
 01183                args.Append(filterDevArgs);
 1184            }
 01185            else if (optHwaccelType == HardwareAccelerationType.videotoolbox)
 1186            {
 01187                if (!isMacOS || !_mediaEncoder.SupportsHwaccel("videotoolbox"))
 1188                {
 01189                    return string.Empty;
 1190                }
 1191
 01192                var isVideotoolboxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 01193                var isVideotoolboxEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 01194                if (!isVideotoolboxDecoder && !isVideotoolboxEncoder)
 1195                {
 01196                    return string.Empty;
 1197                }
 1198
 1199                // videotoolbox hw filter does not require device selection
 01200                args.Append(GetVideoToolboxDeviceArgs(VideotoolboxAlias));
 1201            }
 01202            else if (optHwaccelType == HardwareAccelerationType.rkmpp)
 1203            {
 01204                if (!isLinux || !_mediaEncoder.SupportsHwaccel("rkmpp"))
 1205                {
 01206                    return string.Empty;
 1207                }
 1208
 01209                var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 01210                var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 01211                if (!isRkmppDecoder && !isRkmppEncoder)
 1212                {
 01213                    return string.Empty;
 1214                }
 1215
 01216                args.Append(GetRkmppDeviceArgs(RkmppAlias));
 1217
 01218                var filterDevArgs = string.Empty;
 01219                var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
 1220
 01221                if (doOclTonemap && !isRkmppDecoder)
 1222                {
 01223                    args.Append(GetOpenclDeviceArgs(0, null, RkmppAlias, OpenclAlias));
 01224                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1225                }
 1226
 01227                args.Append(filterDevArgs);
 1228            }
 1229
 01230            if (!string.IsNullOrEmpty(vidDecoder))
 1231            {
 01232                args.Append(vidDecoder);
 1233            }
 1234
 01235            return args.ToString().Trim();
 1236        }
 1237
 1238        /// <summary>
 1239        /// Gets the input argument.
 1240        /// </summary>
 1241        /// <param name="state">Encoding state.</param>
 1242        /// <param name="options">Encoding options.</param>
 1243        /// <param name="segmentContainer">Segment Container.</param>
 1244        /// <returns>Input arguments.</returns>
 1245        public string GetInputArgument(EncodingJobInfo state, EncodingOptions options, string segmentContainer)
 1246        {
 41247            var arg = new StringBuilder();
 41248            var inputVidHwaccelArgs = GetInputVideoHwaccelArgs(state, options);
 1249
 41250            if (!string.IsNullOrEmpty(inputVidHwaccelArgs))
 1251            {
 01252                arg.Append(inputVidHwaccelArgs);
 1253            }
 1254
 41255            var canvasArgs = GetGraphicalSubCanvasSize(state);
 41256            if (!string.IsNullOrEmpty(canvasArgs))
 1257            {
 01258                arg.Append(canvasArgs);
 1259            }
 1260
 41261            if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
 1262            {
 01263                var concatFilePath = Path.Join(_configurationManager.CommonApplicationPaths.CachePath, "concat", state.M
 01264                if (!File.Exists(concatFilePath))
 1265                {
 01266                    _mediaEncoder.GenerateConcatConfig(state.MediaSource, concatFilePath);
 1267                }
 1268
 01269                arg.Append(" -f concat -safe 0 -i \"")
 01270                    .Append(concatFilePath)
 01271                    .Append("\" ");
 1272            }
 1273            else
 1274            {
 41275                arg.Append(" -i ")
 41276                    .Append(_mediaEncoder.GetInputPathArgument(state));
 1277            }
 1278
 41279            if (NeedsExternalSubtitleMuxing(state))
 1280            {
 41281                var subtitlePath = state.SubtitleStream.Path;
 41282                var isGraphicalBurnIn = ShouldEncodeSubtitle(state) && !state.SubtitleStream.IsTextSubtitleStream;
 1283
 1284                // dvdsub/vobsub graphical subtitles use .sub+.idx pairs
 41285                var subtitleExtension = Path.GetExtension(subtitlePath.AsSpan());
 41286                if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase))
 1287                {
 41288                    var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
 41289                    if (File.Exists(idxFile))
 1290                    {
 21291                        subtitlePath = idxFile;
 1292                    }
 1293                }
 1294
 1295                // Use analyzeduration also for subtitle streams to improve resolution detection  with streams inside MK
 41296                var analyzeDurationArgument = GetFfmpegAnalyzeDurationArg(state);
 41297                if (!string.IsNullOrEmpty(analyzeDurationArgument))
 1298                {
 01299                    arg.Append(' ').Append(analyzeDurationArgument);
 1300                }
 1301
 1302                // Apply probesize, too, if configured
 41303                var ffmpegProbeSizeArgument = GetFfmpegProbesizeArg();
 41304                if (!string.IsNullOrEmpty(ffmpegProbeSizeArgument))
 1305                {
 01306                    arg.Append(' ').Append(ffmpegProbeSizeArgument);
 1307                }
 1308
 1309                // Also seek the external subtitles stream.
 41310                var seekSubParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 41311                if (!string.IsNullOrEmpty(seekSubParam))
 1312                {
 01313                    arg.Append(' ').Append(seekSubParam);
 1314                }
 1315
 41316                if (isGraphicalBurnIn && !string.IsNullOrEmpty(canvasArgs))
 1317                {
 01318                    arg.Append(canvasArgs);
 1319                }
 1320
 41321                arg.Append(" -i file:\"").Append(subtitlePath).Append('\"');
 1322            }
 1323
 41324            if (state.AudioStream is not null && state.AudioStream.IsExternal)
 1325            {
 1326                // Also seek the external audio stream.
 01327                var seekAudioParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01328                if (!string.IsNullOrEmpty(seekAudioParam))
 1329                {
 01330                    arg.Append(' ').Append(seekAudioParam);
 1331                }
 1332
 01333                arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
 1334            }
 1335
 1336            // Disable auto inserted SW scaler for HW decoders in case of changed resolution.
 41337            var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
 41338            if (!isSwDecoder)
 1339            {
 01340                arg.Append(" -noautoscale");
 1341            }
 1342
 41343            return arg.ToString();
 1344        }
 1345
 1346        /// <summary>
 1347        /// Determines whether the specified stream is H264.
 1348        /// </summary>
 1349        /// <param name="stream">The stream.</param>
 1350        /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
 1351        public static bool IsH264(MediaStream stream)
 1352        {
 01353            var codec = stream.Codec ?? string.Empty;
 1354
 01355            return codec.Contains("264", StringComparison.OrdinalIgnoreCase)
 01356                    || codec.Contains("avc", StringComparison.OrdinalIgnoreCase);
 1357        }
 1358
 1359        public static bool IsH265(MediaStream stream)
 1360        {
 01361            var codec = stream.Codec ?? string.Empty;
 1362
 01363            return codec.Contains("265", StringComparison.OrdinalIgnoreCase)
 01364                || codec.Contains("hevc", StringComparison.OrdinalIgnoreCase);
 1365        }
 1366
 1367        public static bool IsAv1(MediaStream stream)
 1368        {
 01369            var codec = stream.Codec ?? string.Empty;
 1370
 01371            return codec.Contains("av1", StringComparison.OrdinalIgnoreCase);
 1372        }
 1373
 1374        public static bool IsAAC(MediaStream stream)
 1375        {
 101376            var codec = stream.Codec ?? string.Empty;
 1377
 101378            return codec.Contains("aac", StringComparison.OrdinalIgnoreCase);
 1379        }
 1380
 1381        public static bool IsDoviWithHdr10Bl(MediaStream stream)
 1382        {
 01383            var rangeType = stream?.VideoRangeType;
 1384
 01385            return rangeType is VideoRangeType.DOVIWithHDR10
 01386                or VideoRangeType.DOVIWithEL
 01387                or VideoRangeType.DOVIWithHDR10Plus
 01388                or VideoRangeType.DOVIWithELHDR10Plus
 01389                or VideoRangeType.DOVIInvalid;
 1390        }
 1391
 1392        public static bool IsDovi(MediaStream stream)
 1393        {
 01394            var rangeType = stream?.VideoRangeType;
 1395
 01396            return IsDoviWithHdr10Bl(stream)
 01397                   || (rangeType is VideoRangeType.DOVI
 01398                       or VideoRangeType.DOVIWithHLG
 01399                       or VideoRangeType.DOVIWithSDR);
 1400        }
 1401
 1402        public static bool IsHdr10Plus(MediaStream stream)
 1403        {
 01404            var rangeType = stream?.VideoRangeType;
 1405
 01406            return rangeType is VideoRangeType.HDR10Plus
 01407                       or VideoRangeType.DOVIWithHDR10Plus
 01408                       or VideoRangeType.DOVIWithELHDR10Plus;
 1409        }
 1410
 1411        /// <summary>
 1412        /// Check if dynamic HDR metadata should be removed during stream copy.
 1413        /// Please note this check assumes the range check has already been done
 1414        /// and trivial fallbacks like HDR10+ to HDR10, DOVIWithHDR10 to HDR10 is already checked.
 1415        /// </summary>
 1416        private static DynamicHdrMetadataRemovalPlan ShouldRemoveDynamicHdrMetadata(EncodingJobInfo state)
 1417        {
 01418            var videoStream = state.VideoStream;
 01419            if (videoStream.VideoRange is not VideoRange.HDR)
 1420            {
 01421                return DynamicHdrMetadataRemovalPlan.None;
 1422            }
 1423
 01424            var requestedRangeTypes = state.GetRequestedRangeTypes(state.VideoStream.Codec);
 01425            if (requestedRangeTypes.Length == 0)
 1426            {
 01427                return DynamicHdrMetadataRemovalPlan.None;
 1428            }
 1429
 01430            var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ordinal
 01431            var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIg
 01432            var requestHasDOVIwithEL = requestedRangeTypes.Contains(VideoRangeType.DOVIWithEL.ToString(), StringComparis
 01433            var requestHasDOVIwithELHDR10plus = requestedRangeTypes.Contains(VideoRangeType.DOVIWithELHDR10Plus.ToString
 1434
 01435            var shouldRemoveHdr10Plus = false;
 1436            // Case 1: Client supports HDR10, does not support DOVI with EL but EL presets
 01437            var shouldRemoveDovi = (!requestHasDOVIwithEL && requestHasHDR10) && videoStream.VideoRangeType == VideoRang
 1438
 1439            // Case 2: Client supports DOVI, does not support broken DOVI config
 1440            // Client does not report DOVI support should be allowed to copy bad data for remuxing as HDR10 players woul
 01441            shouldRemoveDovi = shouldRemoveDovi || (requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVII
 1442
 1443            // Special case: we have a video with both EL and HDR10+
 1444            // If the client supports EL but not in the case of coexistence with HDR10+, remove HDR10+ for compatibility
 1445            // Otherwise, remove DOVI if the client is not a DOVI player
 01446            if (videoStream.VideoRangeType == VideoRangeType.DOVIWithELHDR10Plus)
 1447            {
 01448                shouldRemoveHdr10Plus = requestHasDOVIwithEL && !requestHasDOVIwithELHDR10plus;
 01449                shouldRemoveDovi = shouldRemoveDovi || !shouldRemoveHdr10Plus;
 1450            }
 1451
 01452            if (shouldRemoveDovi)
 1453            {
 01454                return DynamicHdrMetadataRemovalPlan.RemoveDovi;
 1455            }
 1456
 1457            // If the client is a Dolby Vision Player, remove the HDR10+ metadata to avoid playback issues
 01458            shouldRemoveHdr10Plus = shouldRemoveHdr10Plus || (requestHasDOVI && videoStream.VideoRangeType == VideoRange
 01459            return shouldRemoveHdr10Plus ? DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus : DynamicHdrMetadataRemovalPlan
 1460        }
 1461
 1462        private bool CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan plan, MediaStream videoStream)
 1463        {
 01464            return plan switch
 01465            {
 01466                DynamicHdrMetadataRemovalPlan.RemoveDovi => _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFil
 01467                                                            || (IsH265(videoStream) && _mediaEncoder.SupportsBitStreamFi
 01468                                                            || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStreamFil
 01469                DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus => (IsH265(videoStream) && _mediaEncoder.SupportsBitStream
 01470                                                                 || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStre
 01471                _ => true,
 01472            };
 1473        }
 1474
 1475        public bool IsDoviRemoved(EncodingJobInfo state)
 1476        {
 01477            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01478                                              && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Remove
 1479        }
 1480
 1481        public bool IsHdr10PlusRemoved(EncodingJobInfo state)
 1482        {
 01483            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01484                                                  && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Re
 1485        }
 1486
 1487        public string GetBitStreamArgs(EncodingJobInfo state, MediaStreamType streamType)
 1488        {
 01489            if (state is null)
 1490            {
 01491                return null;
 1492            }
 1493
 01494            var stream = streamType switch
 01495            {
 01496                MediaStreamType.Audio => state.AudioStream,
 01497                MediaStreamType.Video => state.VideoStream,
 01498                _ => state.VideoStream
 01499            };
 1500            // TODO This is auto inserted into the mpegts mux so it might not be needed.
 1501            // https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
 01502            if (IsH264(stream))
 1503            {
 01504                return "-bsf:v h264_mp4toannexb";
 1505            }
 1506
 01507            if (IsAAC(stream))
 1508            {
 1509                // Convert adts header(mpegts) to asc header(mp4).
 01510                return "-bsf:a aac_adtstoasc";
 1511            }
 1512
 01513            if (IsH265(stream))
 1514            {
 01515                var filter = "-bsf:v hevc_mp4toannexb";
 1516
 1517                // The following checks are not complete because the copy would be rejected
 1518                // if the encoder cannot remove required metadata.
 1519                // And if bsf is used, we must already be using copy codec.
 01520                switch (ShouldRemoveDynamicHdrMetadata(state))
 1521                {
 1522                    default:
 1523                    case DynamicHdrMetadataRemovalPlan.None:
 1524                        break;
 1525                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01526                        filter += _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.HevcMetadata
 01527                            ? ",hevc_metadata=remove_dovi=1"
 01528                            : ",dovi_rpu=strip=1";
 01529                        break;
 1530                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01531                        filter += ",hevc_metadata=remove_hdr10plus=1";
 1532                        break;
 1533                }
 1534
 01535                return filter;
 1536            }
 1537
 01538            if (IsAv1(stream))
 1539            {
 01540                switch (ShouldRemoveDynamicHdrMetadata(state))
 1541                {
 1542                    default:
 1543                    case DynamicHdrMetadataRemovalPlan.None:
 01544                        return null;
 1545                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01546                        return _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.Av1MetadataRemo
 01547                            ? "-bsf:v av1_metadata=remove_dovi=1"
 01548                            : "-bsf:v dovi_rpu=strip=1";
 1549                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01550                        return "-bsf:v av1_metadata=remove_hdr10plus=1";
 1551                }
 1552            }
 1553
 01554            return null;
 1555        }
 1556
 1557        public string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceConta
 1558        {
 121559            var filters = new List<string>();
 1560
 121561            var noiseFilter = GetCopiedAudioTrimBsf(state);
 121562            if (!string.IsNullOrEmpty(noiseFilter))
 1563            {
 61564                filters.Add(noiseFilter);
 1565            }
 1566
 121567            var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
 1568
 1569            // Apply aac_adtstoasc bitstream filter when media source is in mpegts.
 121570            if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)
 121571                && (string.Equals(mediaSourceContainer, "ts", StringComparison.OrdinalIgnoreCase)
 121572                    || string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase)
 121573                    || string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase))
 121574                && IsAAC(state.AudioStream))
 1575            {
 91576                filters.Add("aac_adtstoasc");
 1577            }
 1578
 121579            return filters.Count == 0
 121580                ? string.Empty
 121581                : " -bsf:a " + string.Join(',', filters);
 1582        }
 1583
 1584        // When video is transcoded, accurate_seek (the default) trims video to the
 1585        // exact seek point via decoder-side frame discard. But stream-copied audio
 1586        // bypasses the decoder, so it starts from the nearest keyframe — potentially
 1587        // seconds before the target. Use the noise bsf to drop copied audio packets
 1588        // before the seek target, achieving the same trim precision without
 1589        // re-encoding. The noise bsf's drop= parameter requires ffmpeg >= 5.0.
 1590        // Important: make sure not to use it with wtv because it breaks seeking
 1591        private string GetCopiedAudioTrimBsf(EncodingJobInfo state)
 1592        {
 121593            if (state.TranscodingType is not TranscodingJobType.Hls
 121594                || !state.IsVideoRequest
 121595                || IsCopyCodec(state.OutputVideoCodec)
 121596                || !IsCopyCodec(state.OutputAudioCodec)
 121597                || string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase)
 121598                || _mediaEncoder.EncoderVersion < _minFFmpegNoiseBsfDrop)
 1599            {
 51600                return null;
 1601            }
 1602
 71603            var startTicks = state.BaseRequest.StartTimeTicks ?? 0;
 71604            if (startTicks <= 0)
 1605            {
 11606                return null;
 1607            }
 1608
 61609            var seekSeconds = startTicks / (double)TimeSpan.TicksPerSecond;
 61610            return string.Format(
 61611                CultureInfo.InvariantCulture,
 61612                "noise=drop='lt(pts*tb\\,{0:F3})'",
 61613                seekSeconds);
 1614        }
 1615
 1616        public static string GetSegmentFileExtension(string segmentContainer)
 1617        {
 121618            if (!string.IsNullOrWhiteSpace(segmentContainer))
 1619            {
 121620                return "." + segmentContainer;
 1621            }
 1622
 01623            return ".ts";
 1624        }
 1625
 1626        private string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
 1627        {
 01628            if (state.OutputVideoBitrate is null)
 1629            {
 01630                return string.Empty;
 1631            }
 1632
 01633            int bitrate = state.OutputVideoBitrate.Value;
 1634
 1635            // Bit rate under 1000k is not allowed in h264_qsv.
 01636            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 1637            {
 01638                bitrate = Math.Max(bitrate, 1000);
 1639            }
 1640
 1641            // Currently use the same buffer size for all non-QSV encoders.
 1642            // Use long arithmetic to prevent int32 overflow for very high bitrate values.
 01643            int bufsize = (int)Math.Min((long)bitrate * 2, int.MaxValue);
 1644
 01645            if (string.Equals(videoCodec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1646            {
 01647                return FormattableString.Invariant($" -b:v {bitrate} -bufsize {bufsize}");
 1648            }
 1649
 01650            if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)
 01651                || string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
 1652            {
 01653                return FormattableString.Invariant($" -maxrate {bitrate} -bufsize {bufsize}");
 1654            }
 1655
 01656            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01657                || string.Equals(videoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 01658                || string.Equals(videoCodec, "av1_qsv", StringComparison.OrdinalIgnoreCase))
 1659            {
 1660                // TODO: probe QSV encoders' capabilities and enable more tuning options
 1661                // See also https://github.com/intel/media-delivery/blob/master/doc/quality.rst
 1662
 1663                // Enable MacroBlock level bitrate control for better subjective visual quality
 01664                var mbbrcOpt = string.Empty;
 01665                if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01666                    || string.Equals(videoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 1667                {
 01668                    mbbrcOpt = " -mbbrc 1";
 1669                }
 1670
 1671                // Some less powerful H.264 HW decoders require strict CPB size
 1672                // So bufsize optimizations should not be applied to them
 01673                int factor = 2;
 01674                var codec = state.ActualOutputVideoCodec;
 01675                var level = state.GetRequestedLevel(codec);
 01676                if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)
 01677                    && double.TryParse(level, CultureInfo.InvariantCulture, out double requestedLevel)
 01678                    && requestedLevel < 51)
 1679                {
 01680                    factor = 1;
 1681                }
 1682
 1683                // Set (maxrate == bitrate + 1) to trigger VBR for better bitrate allocation
 1684                // Set (rc_init_occupancy == 2 * bitrate) and (bufsize == 4 * bitrate) to deal with drastic scene change
 1685                // Use long arithmetic and clamp to int.MaxValue to prevent int32 overflow
 1686                // (e.g. bitrate * 4 wraps to a negative value for bitrates above ~537 million)
 01687                int qsvMaxrate = (int)Math.Min((long)bitrate + 1, int.MaxValue);
 01688                int qsvInitOcc = (int)Math.Min((long)bitrate * 1 * factor, int.MaxValue);
 01689                int qsvBufsize = (int)Math.Min((long)bitrate * 2 * factor, int.MaxValue);
 1690
 01691                return FormattableString.Invariant($"{mbbrcOpt} -b:v {bitrate} -maxrate {qsvMaxrate} -rc_init_occupancy 
 1692            }
 1693
 01694            if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01695                || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase))
 1696            {
 1697                // Override the too high default qmin 18 in transcoding preset in legacy h26x_amf
 01698                return FormattableString.Invariant($" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsiz
 1699            }
 1700
 01701            if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01702                || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01703                || string.Equals(videoCodec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1704            {
 1705                // VBR in i965 driver may result in pixelated output.
 01706                if (_mediaEncoder.IsVaapiDeviceInteli965)
 1707                {
 01708                    return FormattableString.Invariant($" -rc_mode CBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsi
 1709                }
 1710
 01711                return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"
 1712            }
 1713
 01714            if (string.Equals(videoCodec, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 01715                || string.Equals(videoCodec, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase))
 1716            {
 1717                // The `maxrate` and `bufsize` options can potentially lead to performance regression
 1718                // and even encoder hangs, especially when the value is very high.
 01719                return FormattableString.Invariant($" -b:v {bitrate} -qmin -1 -qmax -1");
 1720            }
 1721
 01722            return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
 1723        }
 1724
 1725        private string GetEncoderParam(EncoderPreset? preset, EncoderPreset defaultPreset, EncodingOptions encodingOptio
 1726        {
 01727            var param = string.Empty;
 01728            var encoderPreset = preset ?? defaultPreset;
 01729            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
 1730            {
 01731                var presetString = encoderPreset switch
 01732                {
 01733                    EncoderPreset.auto => EncoderPreset.veryfast.ToString().ToLowerInvariant(),
 01734                    _ => encoderPreset.ToString().ToLowerInvariant()
 01735                };
 1736
 01737                param += " -preset " + presetString;
 1738
 01739                int encodeCrf = encodingOptions.H264Crf;
 01740                if (isLibX265)
 1741                {
 01742                    encodeCrf = encodingOptions.H265Crf;
 1743                }
 1744
 01745                if (encodeCrf >= 0 && encodeCrf <= 51)
 1746                {
 01747                    param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture);
 1748                }
 1749                else
 1750                {
 01751                    string defaultCrf = "23";
 01752                    if (isLibX265)
 1753                    {
 01754                        defaultCrf = "28";
 1755                    }
 1756
 01757                    param += " -crf " + defaultCrf;
 1758                }
 1759            }
 01760            else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1761            {
 1762                // Default to use the recommended preset 10.
 1763                // Omit presets < 5, which are too slow for on the fly encoding.
 1764                // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md
 01765                param += encoderPreset switch
 01766                {
 01767                    EncoderPreset.veryslow => " -preset 5",
 01768                    EncoderPreset.slower => " -preset 6",
 01769                    EncoderPreset.slow => " -preset 7",
 01770                    EncoderPreset.medium => " -preset 8",
 01771                    EncoderPreset.fast => " -preset 9",
 01772                    EncoderPreset.faster => " -preset 10",
 01773                    EncoderPreset.veryfast => " -preset 11",
 01774                    EncoderPreset.superfast => " -preset 12",
 01775                    EncoderPreset.ultrafast => " -preset 13",
 01776                    _ => " -preset 10"
 01777                };
 1778            }
 01779            else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01780                     || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01781                     || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1782            {
 1783                // -compression_level is not reliable on AMD.
 01784                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 1785                {
 01786                    param += encoderPreset switch
 01787                    {
 01788                        EncoderPreset.veryslow => " -compression_level 1",
 01789                        EncoderPreset.slower => " -compression_level 2",
 01790                        EncoderPreset.slow => " -compression_level 3",
 01791                        EncoderPreset.medium => " -compression_level 4",
 01792                        EncoderPreset.fast => " -compression_level 5",
 01793                        EncoderPreset.faster => " -compression_level 6",
 01794                        EncoderPreset.veryfast => " -compression_level 7",
 01795                        EncoderPreset.superfast => " -compression_level 7",
 01796                        EncoderPreset.ultrafast => " -compression_level 7",
 01797                        _ => string.Empty
 01798                    };
 1799                }
 1800            }
 01801            else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv)
 01802                     || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv)
 01803                     || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv)
 1804            {
 01805                EncoderPreset[] valid_presets = [EncoderPreset.veryslow, EncoderPreset.slower, EncoderPreset.slow, Encod
 1806
 01807                param += " -preset " + (valid_presets.Contains(encoderPreset) ? encoderPreset : EncoderPreset.veryfast).
 1808            }
 01809            else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
 01810                        || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_n
 01811                        || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase) // av1 (av1_nven
 01812            )
 1813            {
 01814                param += encoderPreset switch
 01815                {
 01816                    EncoderPreset.veryslow => " -preset p7",
 01817                    EncoderPreset.slower => " -preset p6",
 01818                    EncoderPreset.slow => " -preset p5",
 01819                    EncoderPreset.medium => " -preset p4",
 01820                    EncoderPreset.fast => " -preset p3",
 01821                    EncoderPreset.faster => " -preset p2",
 01822                    _ => " -preset p1"
 01823                };
 1824            }
 01825            else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf)
 01826                        || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf
 01827                        || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase) // av1 (av1_amf)
 01828            )
 1829            {
 01830                param += encoderPreset switch
 01831                {
 01832                    EncoderPreset.veryslow => " -quality quality",
 01833                    EncoderPreset.slower => " -quality quality",
 01834                    EncoderPreset.slow => " -quality quality",
 01835                    EncoderPreset.medium => " -quality balanced",
 01836                    _ => " -quality speed"
 01837                };
 1838
 01839                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01840                    || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1841                {
 01842                    param += " -header_insertion_mode gop";
 1843                }
 1844
 01845                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
 1846                {
 01847                    param += " -gops_per_idr 1";
 1848                }
 1849            }
 01850            else if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase) // h264 (h264_
 01851                        || string.Equals(videoEncoder, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase) // hevc 
 01852            )
 1853            {
 01854                param += encoderPreset switch
 01855                {
 01856                    EncoderPreset.veryslow => " -prio_speed 0",
 01857                    EncoderPreset.slower => " -prio_speed 0",
 01858                    EncoderPreset.slow => " -prio_speed 0",
 01859                    EncoderPreset.medium => " -prio_speed 0",
 01860                    _ => " -prio_speed 1"
 01861                };
 1862            }
 1863
 01864            return param;
 1865        }
 1866
 1867        public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
 1868        {
 01869            if (!double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel))
 1870            {
 01871                return null;
 1872            }
 1873
 01874            if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase))
 1875            {
 1876                // Transcode to level 5.3 (15) and lower for maximum compatibility.
 1877                // https://en.wikipedia.org/wiki/AV1#Levels
 01878                if (requestLevel < 0 || requestLevel >= 15)
 1879                {
 01880                    return "15";
 1881                }
 1882            }
 01883            else if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 01884                     || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
 1885            {
 1886                // Transcode to level 5.0 and lower for maximum compatibility.
 1887                // Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it.
 1888                // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
 1889                // MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880.
 01890                if (requestLevel < 0 || requestLevel >= 150)
 1891                {
 01892                    return "150";
 1893                }
 1894            }
 01895            else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
 1896            {
 1897                // Transcode to level 5.1 and lower for maximum compatibility.
 1898                // h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
 1899                // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
 01900                if (requestLevel < 0 || requestLevel >= 51)
 1901                {
 01902                    return "51";
 1903                }
 1904            }
 1905
 01906            return level;
 1907        }
 1908
 1909        /// <summary>
 1910        /// Gets the text subtitle param.
 1911        /// </summary>
 1912        /// <param name="state">The state.</param>
 1913        /// <param name="enableAlpha">Enable alpha processing.</param>
 1914        /// <param name="enableSub2video">Enable sub2video mode.</param>
 1915        /// <returns>System.String.</returns>
 1916        public string GetTextSubtitlesFilter(EncodingJobInfo state, bool enableAlpha, bool enableSub2video)
 1917        {
 01918            var seconds = Math.Round(TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds);
 1919
 1920            // hls always copies timestamps
 01921            var setPtsParam = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive
 01922                ? string.Empty
 01923                : string.Format(CultureInfo.InvariantCulture, ",setpts=PTS -{0}/TB", seconds);
 1924
 01925            var alphaParam = enableAlpha ? ":alpha=1" : string.Empty;
 01926            var sub2videoParam = enableSub2video ? ":sub2video=1" : string.Empty;
 1927
 01928            var fontPath = _pathManager.GetAttachmentFolderPath(state.MediaSource.Id);
 01929            var fontParam = fontPath is null
 01930                ? string.Empty
 01931                : string.Format(
 01932                    CultureInfo.InvariantCulture,
 01933                    ":fontsdir='{0}'",
 01934                    _mediaEncoder.EscapeSubtitleFilterPath(fontPath));
 1935
 01936            if (state.SubtitleStream.IsExternal)
 1937            {
 01938                var charsetParam = string.Empty;
 1939
 01940                if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
 1941                {
 01942                    var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
 01943                            state.SubtitleStream,
 01944                            state.SubtitleStream.Language,
 01945                            state.MediaSource,
 01946                            CancellationToken.None).GetAwaiter().GetResult();
 1947
 01948                    if (!string.IsNullOrEmpty(charenc))
 1949                    {
 01950                        charsetParam = ":charenc=" + charenc;
 1951                    }
 1952                }
 1953
 01954                return string.Format(
 01955                    CultureInfo.InvariantCulture,
 01956                    "subtitles=f='{0}'{1}{2}{3}{4}{5}",
 01957                    _mediaEncoder.EscapeSubtitleFilterPath(state.SubtitleStream.Path),
 01958                    charsetParam,
 01959                    alphaParam,
 01960                    sub2videoParam,
 01961                    fontParam,
 01962                    setPtsParam);
 1963            }
 1964
 01965            var subtitlePath = _subtitleEncoder.GetSubtitleFilePath(
 01966                    state.SubtitleStream,
 01967                    state.MediaSource,
 01968                    CancellationToken.None).GetAwaiter().GetResult();
 1969
 01970            return string.Format(
 01971                CultureInfo.InvariantCulture,
 01972                "subtitles=f='{0}'{1}{2}{3}{4}",
 01973                _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
 01974                alphaParam,
 01975                sub2videoParam,
 01976                fontParam,
 01977                setPtsParam);
 1978        }
 1979
 1980        public double? GetFramerateParam(EncodingJobInfo state)
 1981        {
 01982            var request = state.BaseRequest;
 1983
 01984            if (request.Framerate.HasValue)
 1985            {
 01986                return request.Framerate.Value;
 1987            }
 1988
 01989            var maxrate = request.MaxFramerate;
 1990
 01991            if (maxrate.HasValue && state.VideoStream is not null)
 1992            {
 01993                var contentRate = state.VideoStream.ReferenceFrameRate;
 1994
 01995                if (contentRate.HasValue && contentRate.Value > maxrate.Value)
 1996                {
 01997                    return maxrate;
 1998                }
 1999            }
 2000
 02001            return null;
 2002        }
 2003
 2004        public string GetHlsVideoKeyFrameArguments(
 2005            EncodingJobInfo state,
 2006            string codec,
 2007            int segmentLength,
 2008            bool isEventPlaylist,
 2009            int? startNumber)
 2010        {
 02011            var args = string.Empty;
 02012            var gopArg = string.Empty;
 2013
 02014            var keyFrameArg = string.Format(
 02015                CultureInfo.InvariantCulture,
 02016                " -force_key_frames:0 \"expr:gte(t,n_forced*{0})\"",
 02017                segmentLength);
 2018
 02019            var framerate = state.VideoStream?.RealFrameRate;
 02020            if (framerate.HasValue)
 2021            {
 2022                // This is to make sure keyframe interval is limited to our segment,
 2023                // as forcing keyframes is not enough.
 2024                // Example: we encoded half of desired length, then codec detected
 2025                // scene cut and inserted a keyframe; next forced keyframe would
 2026                // be created outside of segment, which breaks seeking.
 02027                gopArg = string.Format(
 02028                    CultureInfo.InvariantCulture,
 02029                    " -g:v:0 {0} -keyint_min:v:0 {0}",
 02030                    Math.Ceiling(segmentLength * framerate.Value));
 2031            }
 2032
 2033            // Unable to force key frames using these encoders, set key frames by GOP.
 02034            if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02035                || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02036                || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02037                || string.Equals(codec, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 02038                || string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 02039                || string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 02040                || string.Equals(codec, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase)
 02041                || string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 02042                || string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 02043                || string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase)
 02044                || string.Equals(codec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 2045            {
 02046                args += gopArg;
 2047            }
 02048            else if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase)
 02049                     || string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)
 02050                     || string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02051                     || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02052                     || string.Equals(codec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 2053            {
 02054                args += keyFrameArg;
 2055
 2056                // prevent the libx264 from post processing to break the set keyframe.
 02057                if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase))
 2058                {
 02059                    args += " -sc_threshold:v:0 0";
 2060                }
 2061            }
 2062            else
 2063            {
 02064                args += keyFrameArg + gopArg;
 2065            }
 2066
 2067            // The in-band Parameter Sets generated by the AMD HEVC VA-API encoder is inconsistent
 2068            // with the extradata generated by ffmpeg, causing decoding failures when using hvc1.
 02069            if (string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02070                && _mediaEncoder.IsVaapiDeviceAmd)
 2071            {
 2072                // Extracting the extradata from the in-band PS to bypass the issue.
 2073                // This can be removed once the issue is resolved in libva or Mesa.
 2074                // Transcoding is unavoidable here, so using BSF will not conflict with BSF in remuxing.
 02075                args += " -flags:v -global_header -bsf:v extract_extradata=remove=0";
 2076            }
 2077
 02078            return args;
 2079        }
 2080
 2081        /// <summary>
 2082        /// Gets the video bitrate to specify on the command line.
 2083        /// </summary>
 2084        /// <param name="state">Encoding state.</param>
 2085        /// <param name="videoEncoder">Video encoder to use.</param>
 2086        /// <param name="encodingOptions">Encoding options.</param>
 2087        /// <param name="defaultPreset">Default present to use for encoding.</param>
 2088        /// <returns>Video bitrate.</returns>
 2089        public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, 
 2090        {
 02091            var param = string.Empty;
 2092
 2093            // Tutorials: Enable Intel GuC / HuC firmware loading for Low Power Encoding.
 2094            // https://01.org/group/43/downloads/firmware
 2095            // https://wiki.archlinux.org/title/intel_graphics#Enable_GuC_/_HuC_firmware_loading
 2096            // Intel Low Power Encoding can save unnecessary CPU-GPU synchronization,
 2097            // which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
 02098            var intelLowPowerHwEncoding = false;
 2099
 2100            // Workaround for linux 5.18 to 6.1.3 i915 hang at cost of performance.
 2101            // https://github.com/intel/media-driver/issues/1456
 02102            var enableWaFori915Hang = false;
 2103
 02104            var hardwareAccelerationType = encodingOptions.HardwareAccelerationType;
 2105
 02106            if (hardwareAccelerationType == HardwareAccelerationType.vaapi)
 2107            {
 02108                var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
 2109
 02110                if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
 2111                {
 02112                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder && isIntelVaapiDriver;
 2113                }
 02114                else if (string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
 2115                {
 02116                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder && isIntelVaapiDriver;
 2117                }
 2118            }
 02119            else if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 2120            {
 02121                if (OperatingSystem.IsLinux())
 2122                {
 02123                    var ver = Environment.OSVersion.Version;
 02124                    var isFixedKernel60 = ver.Major == 6 && ver.Minor == 0 && ver >= _minFixedKernel60i915Hang;
 02125                    var isUnaffectedKernel = ver < _minKerneli915Hang || ver > _maxKerneli915Hang;
 2126
 02127                    if (!(isUnaffectedKernel || isFixedKernel60))
 2128                    {
 02129                        var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
 02130                        var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
 02131                                             || vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 02132                        var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
 02133                            && IsVaapiSupported(state)
 02134                            && IsOpenclFullSupported()
 02135                            && !IsIntelVppTonemapAvailable(state, encodingOptions)
 02136                            && IsHwTonemapAvailable(state, encodingOptions);
 2137
 02138                        enableWaFori915Hang = isIntelDecoder && doOclTonemap;
 2139                    }
 2140                }
 2141
 02142                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 2143                {
 02144                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder;
 2145                }
 02146                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2147                {
 02148                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder;
 2149                }
 2150                else
 2151                {
 02152                    enableWaFori915Hang = false;
 2153                }
 2154            }
 2155
 02156            if (intelLowPowerHwEncoding)
 2157            {
 02158                param += " -low_power 1";
 2159            }
 2160
 02161            if (enableWaFori915Hang)
 2162            {
 02163                param += " -async_depth 1";
 2164            }
 2165
 02166            var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
 02167            var encodingPreset = encodingOptions.EncoderPreset;
 2168
 02169            param += GetEncoderParam(encodingPreset, defaultPreset, encodingOptions, videoEncoder, isLibX265);
 02170            param += GetVideoBitrateParam(state, videoEncoder);
 2171
 02172            var framerate = GetFramerateParam(state);
 02173            if (framerate.HasValue)
 2174            {
 02175                param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(CultureInfo.Inv
 2176            }
 2177
 02178            var targetVideoCodec = state.ActualOutputVideoCodec;
 02179            if (string.Equals(targetVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
 02180                || string.Equals(targetVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
 2181            {
 02182                targetVideoCodec = "hevc";
 2183            }
 2184
 02185            var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
 02186            profile = WhiteSpaceRegex().Replace(profile, string.Empty).ToLowerInvariant();
 2187
 02188            var videoProfiles = Array.Empty<string>();
 02189            if (string.Equals("h264", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2190            {
 02191                videoProfiles = _videoProfilesH264;
 2192            }
 02193            else if (string.Equals("hevc", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2194            {
 02195                videoProfiles = _videoProfilesH265;
 2196            }
 02197            else if (string.Equals("av1", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2198            {
 02199                videoProfiles = _videoProfilesAv1;
 2200            }
 2201
 02202            if (!videoProfiles.Contains(profile, StringComparison.OrdinalIgnoreCase))
 2203            {
 02204                profile = string.Empty;
 2205            }
 2206
 2207            // We only transcode to HEVC 8-bit for now, force Main Profile.
 02208            if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)
 02209                || profile.Contains("mainstill", StringComparison.OrdinalIgnoreCase))
 2210            {
 02211                profile = "main";
 2212            }
 2213
 2214            // Extended Profile is not supported by any known h264 encoders, force Main Profile.
 02215            if (profile.Contains("extended", StringComparison.OrdinalIgnoreCase))
 2216            {
 02217                profile = "main";
 2218            }
 2219
 2220            // Only libx264 support encoding H264 High 10 Profile, otherwise force High Profile.
 02221            if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02222                && profile.Contains("high10", StringComparison.OrdinalIgnoreCase))
 2223            {
 02224                profile = "high";
 2225            }
 2226
 2227            // We only need Main profile of AV1 encoders.
 02228            if (videoEncoder.Contains("av1", StringComparison.OrdinalIgnoreCase)
 02229                && (profile.Contains("high", StringComparison.OrdinalIgnoreCase)
 02230                    || profile.Contains("professional", StringComparison.OrdinalIgnoreCase)))
 2231            {
 02232                profile = "main";
 2233            }
 2234
 2235            // h264_vaapi does not support Baseline profile, force Constrained Baseline in this case,
 2236            // which is compatible (and ugly).
 02237            if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02238                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2239            {
 02240                profile = "constrained_baseline";
 2241            }
 2242
 2243            // libx264, h264_{qsv,nvenc,rkmpp} does not support Constrained Baseline profile, force Baseline in this cas
 02244            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02245                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02246                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02247                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02248                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2249            {
 02250                profile = "baseline";
 2251            }
 2252
 2253            // libx264, h264_{qsv,nvenc,vaapi,rkmpp} does not support Constrained High profile, force High in this case.
 02254            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02255                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02256                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02257                 || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02258                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02259                && profile.Contains("high", StringComparison.OrdinalIgnoreCase))
 2260            {
 02261                profile = "high";
 2262            }
 2263
 02264            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02265                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2266            {
 02267                profile = "constrained_baseline";
 2268            }
 2269
 02270            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02271                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2272            {
 02273                profile = "constrained_high";
 2274            }
 2275
 02276            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02277                && profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase))
 2278            {
 02279                profile = "constrained_baseline";
 2280            }
 2281
 02282            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02283                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2284            {
 02285                profile = "constrained_high";
 2286            }
 2287
 02288            if (!string.IsNullOrEmpty(profile))
 2289            {
 2290                // Currently there's no profile option in av1_nvenc encoder
 02291                if (!(string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 02292                      || string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)))
 2293                {
 02294                    param += " -profile:v:0 " + profile;
 2295                }
 2296            }
 2297
 02298            var level = NormalizeTranscodingLevel(state, state.GetRequestedLevel(targetVideoCodec));
 2299
 02300            if (!string.IsNullOrEmpty(level))
 2301            {
 2302                // libx264, QSV, AMF can adjust the given level to match the output.
 02303                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02304                    || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2305                {
 02306                    param += " -level " + level;
 2307                }
 02308                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2309                {
 2310                    // hevc_qsv use -level 51 instead of -level 153.
 02311                    if (double.TryParse(level, CultureInfo.InvariantCulture, out double hevcLevel))
 2312                    {
 02313                        param += " -level " + (hevcLevel / 3);
 2314                    }
 2315                }
 02316                else if (string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 02317                         || string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 2318                {
 2319                    // libsvtav1 and av1_qsv use -level 60 instead of -level 16
 2320                    // https://aomedia.org/av1/specification/annex-a/
 02321                    if (int.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out int av1Level))
 2322                    {
 02323                        var x = 2 + (av1Level >> 2);
 02324                        var y = av1Level & 3;
 02325                        var res = (x * 10) + y;
 02326                        param += " -level " + res;
 2327                    }
 2328                }
 02329                else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02330                         || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 02331                         || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 2332                {
 02333                    param += " -level " + level;
 2334                }
 02335                else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02336                         || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 02337                         || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase))
 2338                {
 2339                    // level option may cause NVENC to fail.
 2340                    // NVENC cannot adjust the given level, just throw an error.
 2341                }
 02342                else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02343                         || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02344                         || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 2345                {
 2346                    // level option may cause corrupted frames on AMD VAAPI.
 02347                    if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 2348                    {
 02349                        param += " -level " + level;
 2350                    }
 2351                }
 02352                else if (string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 02353                         || string.Equals(videoEncoder, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase))
 2354                {
 02355                    param += " -level " + level;
 2356                }
 02357                else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2358                {
 02359                    param += " -level " + level;
 2360                }
 2361            }
 2362
 02363            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2364            {
 02365                param += " -x264opts:0 subme=0:me_range=16:rc_lookahead=10:me=hex:open_gop=0";
 2366            }
 2367
 02368            if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2369            {
 2370                // libx265 only accept level option in -x265-params.
 2371                // level option may cause libx265 to fail.
 2372                // libx265 cannot adjust the given level, just throw an error.
 02373                param += " -x265-params:0 no-scenecut=1:no-open-gop=1:no-info=1";
 2374
 02375                if (encodingOptions.EncoderPreset < EncoderPreset.ultrafast)
 2376                {
 2377                    // The following params are slower than the ultrafast preset, don't use when ultrafast is selected.
 02378                    param += ":subme=3:merange=25:rc-lookahead=10:me=star:ctu=32:max-tu-size=32:min-cu-size=16:rskip=2:r
 2379                }
 2380            }
 2381
 02382            if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)
 02383                && _mediaEncoder.EncoderVersion >= _minFFmpegSvtAv1Params)
 2384            {
 02385                param += " -svtav1-params:0 rc=1:tune=0:film-grain=0:enable-overlays=1:enable-tf=0";
 2386            }
 2387
 2388            /* Access unit too large: 8192 < 20880 error */
 02389            if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) ||
 02390                 string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) &&
 02391                 _mediaEncoder.EncoderVersion >= _minFFmpegVaapiH26xEncA53CcSei)
 2392            {
 02393                param += " -sei -a53_cc";
 2394            }
 2395
 02396            return param;
 2397        }
 2398
 2399        public bool CanStreamCopyVideo(EncodingJobInfo state, MediaStream videoStream)
 2400        {
 02401            var request = state.BaseRequest;
 2402
 02403            if (!request.AllowVideoStreamCopy)
 2404            {
 02405                return false;
 2406            }
 2407
 02408            if (videoStream.IsInterlaced
 02409                && state.DeInterlace(videoStream.Codec, false))
 2410            {
 02411                return false;
 2412            }
 2413
 02414            if (videoStream.IsAnamorphic ?? false)
 2415            {
 02416                if (request.RequireNonAnamorphic)
 2417                {
 02418                    return false;
 2419                }
 2420            }
 2421
 2422            // Can't stream copy if we're burning in subtitles
 02423            if (request.SubtitleStreamIndex.HasValue
 02424                && request.SubtitleStreamIndex.Value >= 0
 02425                && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
 2426            {
 02427                return false;
 2428            }
 2429
 02430            if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 02431                && videoStream.IsAVC.HasValue
 02432                && !videoStream.IsAVC.Value
 02433                && request.RequireAvc)
 2434            {
 02435                return false;
 2436            }
 2437
 2438            // Source and target codecs must match
 02439            if (string.IsNullOrEmpty(videoStream.Codec)
 02440                || (state.SupportedVideoCodecs.Length != 0
 02441                    && !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparison.OrdinalIgnoreCase)))
 2442            {
 02443                return false;
 2444            }
 2445
 02446            var requestedProfiles = state.GetRequestedProfiles(videoStream.Codec);
 2447
 2448            // If client is requesting a specific video profile, it must match the source
 02449            if (requestedProfiles.Length > 0)
 2450            {
 02451                if (string.IsNullOrEmpty(videoStream.Profile))
 2452                {
 2453                    // return false;
 2454                }
 2455
 02456                var requestedProfile = requestedProfiles[0];
 2457                // strip spaces because they may be stripped out on the query string as well
 02458                if (!string.IsNullOrEmpty(videoStream.Profile)
 02459                    && !requestedProfiles.Contains(videoStream.Profile.Replace(" ", string.Empty, StringComparison.Ordin
 2460                {
 02461                    var currentScore = GetVideoProfileScore(videoStream.Codec, videoStream.Profile);
 02462                    var requestedScore = GetVideoProfileScore(videoStream.Codec, requestedProfile);
 2463
 02464                    if (currentScore == -1 || currentScore > requestedScore)
 2465                    {
 02466                        return false;
 2467                    }
 2468                }
 2469            }
 2470
 02471            var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
 02472            if (requestedRangeTypes.Length > 0)
 2473            {
 02474                if (videoStream.VideoRangeType == VideoRangeType.Unknown)
 2475                {
 02476                    return false;
 2477                }
 2478
 2479                // DOVIWithHDR10 should be compatible with HDR10 supporting players. Same goes with HLG and of course SD
 02480                var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ord
 02481                var requestHasHLG = requestedRangeTypes.Contains(VideoRangeType.HLG.ToString(), StringComparison.Ordinal
 02482                var requestHasSDR = requestedRangeTypes.Contains(VideoRangeType.SDR.ToString(), StringComparison.Ordinal
 02483                var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.Ordin
 2484
 2485                // If SDR is the only supported range, we should not copy any of the HDR streams.
 2486                // All the following copy check assumes at least one HDR format is supported.
 02487                if (requestedRangeTypes.Length == 1 && requestHasSDR && videoStream.VideoRangeType != VideoRangeType.SDR
 2488                {
 02489                    return false;
 2490                }
 2491
 2492                // If the client does not support DOVI and the video stream is DOVI without fallback, we should not copy
 02493                if (!requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVI)
 2494                {
 02495                    return false;
 2496                }
 2497
 02498                if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreC
 02499                     && !((requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10)
 02500                            || (requestHasHLG && videoStream.VideoRangeType == VideoRangeType.DOVIWithHLG)
 02501                            || (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)
 02502                            || (requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.HDR10Plus)))
 2503                {
 2504                    // If the video stream is in HDR10+ or a static HDR format, don't allow copy if the client does not 
 02505                    if (videoStream.VideoRangeType is VideoRangeType.HDR10Plus or VideoRangeType.HDR10 or VideoRangeType
 2506                    {
 02507                        return false;
 2508                    }
 2509
 2510                    // Check complicated cases where we need to remove dynamic metadata
 2511                    // Conservatively refuse to copy if the encoder can't remove dynamic metadata,
 2512                    // but a removal is required for compatability reasons.
 02513                    var dynamicHdrMetadataRemovalPlan = ShouldRemoveDynamicHdrMetadata(state);
 02514                    if (!CanEncoderRemoveDynamicHdrMetadata(dynamicHdrMetadataRemovalPlan, videoStream))
 2515                    {
 02516                        return false;
 2517                    }
 2518                }
 2519            }
 2520
 02521            var requestedRotations = state.GetRequestedRotations(videoStream.Codec);
 02522            if (requestedRotations.Length > 0)
 2523            {
 02524                var rotation = state.VideoStream?.Rotation ?? 0;
 02525                if (rotation != 0
 02526                    && !requestedRotations.Contains(rotation.ToString(CultureInfo.InvariantCulture), StringComparison.Or
 2527                {
 02528                    return false;
 2529                }
 2530            }
 2531
 2532            // Video width must fall within requested value
 02533            if (request.MaxWidth.HasValue
 02534                && (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
 2535            {
 02536                return false;
 2537            }
 2538
 2539            // Video height must fall within requested value
 02540            if (request.MaxHeight.HasValue
 02541                && (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value))
 2542            {
 02543                return false;
 2544            }
 2545
 2546            // Video framerate must fall within requested value
 02547            var requestedFramerate = request.MaxFramerate ?? request.Framerate;
 02548            if (requestedFramerate.HasValue)
 2549            {
 02550                var videoFrameRate = videoStream.ReferenceFrameRate;
 2551
 2552                // Add a little tolerance to the framerate check because some videos might record a framerate
 2553                // that is slightly greater than the intended framerate, but the device can still play it correctly.
 2554                // 0.05 fps tolerance should be safe enough.
 02555                if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value + 0.05f)
 2556                {
 02557                    return false;
 2558                }
 2559            }
 2560
 2561            // Video bitrate must fall within requested value
 02562            if (request.VideoBitRate.HasValue
 02563                && (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value))
 2564            {
 2565                // For LiveTV that has no bitrate, let's try copy if other conditions are met
 02566                if (string.IsNullOrWhiteSpace(request.LiveStreamId) || videoStream.BitRate.HasValue)
 2567                {
 02568                    return false;
 2569                }
 2570            }
 2571
 02572            var maxBitDepth = state.GetRequestedVideoBitDepth(videoStream.Codec);
 02573            if (maxBitDepth.HasValue)
 2574            {
 02575                if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > maxBitDepth.Value)
 2576                {
 02577                    return false;
 2578                }
 2579            }
 2580
 02581            var maxRefFrames = state.GetRequestedMaxRefFrames(videoStream.Codec);
 02582            if (maxRefFrames.HasValue
 02583                && videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > maxRefFrames.Value)
 2584            {
 02585                return false;
 2586            }
 2587
 2588            // If a specific level was requested, the source must match or be less than
 02589            var level = state.GetRequestedLevel(videoStream.Codec);
 02590            if (double.TryParse(level, CultureInfo.InvariantCulture, out var requestLevel))
 2591            {
 02592                if (!videoStream.Level.HasValue)
 2593                {
 2594                    // return false;
 2595                }
 2596
 02597                if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
 2598                {
 02599                    return false;
 2600                }
 2601            }
 2602
 02603            if (string.Equals(state.InputContainer, "avi", StringComparison.OrdinalIgnoreCase)
 02604                && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)
 02605                && !(videoStream.IsAVC ?? false))
 2606            {
 2607                // see Coach S01E01 - Kelly and the Professor(0).avi
 02608                return false;
 2609            }
 2610
 02611            return true;
 2612        }
 2613
 2614        public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, IEnumerable<string> supportedAudi
 02615            => CanStreamCopyAudio(state, audioStream, supportedAudioCodecs, out _);
 2616
 2617        /// <summary>
 2618        /// Determines whether the given audio stream can be stream-copied and, regardless of the outcome,
 2619        /// reports the codec/parameter incompatibilities that would force a re-encode via <paramref name="failureReason
 2620        /// </summary>
 2621        /// <param name="state">The encoding job state.</param>
 2622        /// <param name="audioStream">The source audio stream.</param>
 2623        /// <param name="supportedAudioCodecs">The audio codecs the target supports.</param>
 2624        /// <param name="failureReasons">The codec/parameter incompatibilities preventing a copy, or <c>0</c> if the str
 2625        /// <returns><c>true</c> if the audio stream can be stream-copied; otherwise, <c>false</c>.</returns>
 2626        public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, IEnumerable<string> supportedAudi
 2627        {
 02628            var request = state.BaseRequest;
 2629
 2630            // Policy-independent compatibility check, so the reasons are reported even when a policy gate is what ultim
 02631            failureReasons = GetAudioStreamCopyFailureReasons(state, audioStream, supportedAudioCodecs);
 2632
 02633            return request.AllowAudioStreamCopy
 02634                && request.EnableAutoStreamCopy
 02635                && failureReasons == 0;
 2636        }
 2637
 2638        private static TranscodeReason GetAudioStreamCopyFailureReasons(EncodingJobInfo state, MediaStream audioStream, 
 2639        {
 02640            var request = state.BaseRequest;
 02641            TranscodeReason reasons = 0;
 2642
 02643            var maxBitDepth = state.GetRequestedAudioBitDepth(audioStream.Codec);
 02644            if (maxBitDepth.HasValue
 02645                && audioStream.BitDepth.HasValue
 02646                && audioStream.BitDepth.Value > maxBitDepth.Value)
 2647            {
 02648                reasons |= TranscodeReason.AudioBitDepthNotSupported;
 2649            }
 2650
 2651            // Source and target codecs must match
 02652            if (string.IsNullOrEmpty(audioStream.Codec)
 02653                || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparison.OrdinalIgnoreCase))
 2654            {
 02655                reasons |= TranscodeReason.AudioCodecNotSupported;
 2656            }
 2657
 2658            // Channels must fall within requested value
 02659            var channels = state.GetRequestedAudioChannels(audioStream.Codec);
 02660            if (channels.HasValue
 02661                && (!audioStream.Channels.HasValue
 02662                    || audioStream.Channels.Value <= 0
 02663                    || audioStream.Channels.Value > channels.Value))
 2664            {
 02665                reasons |= TranscodeReason.AudioChannelsNotSupported;
 2666            }
 2667
 2668            // Sample rate must fall within requested value
 02669            if (request.AudioSampleRate.HasValue
 02670                && (!audioStream.SampleRate.HasValue
 02671                    || audioStream.SampleRate.Value <= 0
 02672                    || audioStream.SampleRate.Value > request.AudioSampleRate.Value))
 2673            {
 02674                reasons |= TranscodeReason.AudioSampleRateNotSupported;
 2675            }
 2676
 2677            // Audio bitrate must fall within requested value
 02678            if (request.AudioBitRate.HasValue
 02679                && audioStream.BitRate.HasValue
 02680                && audioStream.BitRate.Value > request.AudioBitRate.Value)
 2681            {
 02682                reasons |= TranscodeReason.AudioBitrateNotSupported;
 2683            }
 2684
 02685            return reasons;
 2686        }
 2687
 2688        public int GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideo
 2689        {
 02690            var bitrate = request.VideoBitRate;
 2691
 02692            if (videoStream is not null)
 2693            {
 02694                var isUpscaling = request.Height.HasValue
 02695                    && videoStream.Height.HasValue
 02696                    && request.Height.Value > videoStream.Height.Value
 02697                    && request.Width.HasValue
 02698                    && videoStream.Width.HasValue
 02699                    && request.Width.Value > videoStream.Width.Value;
 2700
 2701                // Don't allow bitrate increases unless upscaling
 02702                if (!isUpscaling && bitrate.HasValue && videoStream.BitRate.HasValue)
 2703                {
 02704                    bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value);
 2705                }
 2706
 02707                if (bitrate.HasValue)
 2708                {
 02709                    var inputVideoCodec = videoStream.Codec;
 02710                    bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
 2711
 2712                    // If a max bitrate was requested, don't let the scaled bitrate exceed it
 02713                    if (request.VideoBitRate.HasValue)
 2714                    {
 02715                        bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
 2716                    }
 2717                }
 2718            }
 2719
 2720            // Cap the max target bitrate to 400 Mbps.
 2721            // No consumer or professional hardware transcode target exceeds this value
 2722            // (Intel QSV tops out at ~300 Mbps for H.264; HEVC High Tier Level 5.x is ~240 Mbps).
 2723            // Without this cap, plugin-provided MPEG-TS streams with no usable bitrate metadata
 2724            // can produce unreasonably large -bufsize/-maxrate values for the encoder.
 2725            // Note: the existing FallbackMaxStreamingBitrate mechanism (default 30 Mbps) only
 2726            // applies when a LiveStreamId is set (M3U/HDHR sources). Plugin streams and other
 2727            // sources that bypass the LiveTV pipeline are not covered by it.
 2728            const int MaxSaneBitrate = 400_000_000; // 400 Mbps
 02729            return Math.Min(bitrate ?? 0, MaxSaneBitrate);
 2730        }
 2731
 2732        private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
 2733        {
 2734            // these values were chosen from testing to improve low bitrate streams
 02735            if (sourceBitrate <= 2000000)
 2736            {
 02737                sourceBitrate = Convert.ToInt32(sourceBitrate * 2.5);
 2738            }
 02739            else if (sourceBitrate <= 3000000)
 2740            {
 02741                sourceBitrate *= 2;
 2742            }
 2743
 02744            var bitrate = Math.Min(sourceBitrate, requestedBitrate);
 2745
 02746            return bitrate;
 2747        }
 2748
 2749        private static double GetVideoBitrateScaleFactor(string codec)
 2750        {
 2751            // hevc & vp9 - 40% more efficient than h.264
 02752            if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 02753                || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
 02754                || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
 2755            {
 02756                return .6;
 2757            }
 2758
 2759            // av1 - 50% more efficient than h.264
 02760            if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 2761            {
 02762                return .5;
 2763            }
 2764
 02765            return 1;
 2766        }
 2767
 2768        public static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
 2769        {
 02770            var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
 02771            var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
 2772
 2773            // Don't scale the real bitrate lower than the requested bitrate
 02774            var scaleFactor = Math.Max(outputScaleFactor / inputScaleFactor, 1);
 2775
 02776            if (bitrate <= 500000)
 2777            {
 02778                scaleFactor = Math.Max(scaleFactor, 4);
 2779            }
 02780            else if (bitrate <= 1000000)
 2781            {
 02782                scaleFactor = Math.Max(scaleFactor, 3);
 2783            }
 02784            else if (bitrate <= 2000000)
 2785            {
 02786                scaleFactor = Math.Max(scaleFactor, 2.5);
 2787            }
 02788            else if (bitrate <= 3000000)
 2789            {
 02790                scaleFactor = Math.Max(scaleFactor, 2);
 2791            }
 02792            else if (bitrate >= 30000000)
 2793            {
 2794                // Don't scale beyond 30Mbps, it is hardly visually noticeable for most codecs with our prefer speed enc
 2795                // and will cause extremely high bitrate to be used for av1->h264 transcoding that will overload clients
 02796                scaleFactor = 1;
 2797            }
 2798
 02799            return Convert.ToInt32(scaleFactor * bitrate);
 2800        }
 2801
 2802        public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChanne
 2803        {
 02804            return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels);
 2805        }
 2806
 2807        public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudio
 2808        {
 02809            if (audioStream is null)
 2810            {
 02811                return null;
 2812            }
 2813
 02814            var inputChannels = audioStream.Channels ?? 0;
 02815            var outputChannels = outputAudioChannels ?? 0;
 02816            var bitrate = audioBitRate ?? int.MaxValue;
 2817
 02818            if (string.IsNullOrEmpty(audioCodec)
 02819                || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
 02820                || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
 02821                || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
 02822                || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
 02823                || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
 02824                || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
 2825            {
 2826#pragma warning disable SA1008
 02827                return (inputChannels, outputChannels) switch
 02828                {
 02829                    ( >= 6, >= 6 or 0) => Math.Min(640000, bitrate),
 02830                    ( > 0, > 0) => Math.Min(outputChannels * 128000, bitrate),
 02831                    ( > 0, _) => Math.Min(inputChannels * 128000, bitrate),
 02832                    (_, _) => Math.Min(384000, bitrate)
 02833                };
 2834#pragma warning restore SA1008
 2835            }
 2836
 02837            if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
 02838                || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
 2839            {
 2840#pragma warning disable SA1008
 02841                return (inputChannels, outputChannels) switch
 02842                {
 02843                    ( >= 6, >= 6 or 0) => Math.Min(768000, bitrate),
 02844                    ( > 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
 02845                    ( > 0, _) => Math.Min(inputChannels * 136000, bitrate),
 02846                    (_, _) => Math.Min(672000, bitrate)
 02847                };
 2848#pragma warning restore SA1008
 2849            }
 2850
 2851            // Empty bitrate area is not allow on iOS
 2852            // Default audio bitrate to 128K per channel if we don't have codec specific defaults
 2853            // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
 02854            return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
 2855        }
 2856
 2857        public string GetAudioVbrModeParam(string encoder, int bitrate, int channels)
 2858        {
 02859            var bitratePerChannel = bitrate / Math.Max(channels, 1);
 02860            if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
 2861            {
 02862                return " -vbr:a " + bitratePerChannel switch
 02863                {
 02864                    < 32000 => "1",
 02865                    < 48000 => "2",
 02866                    < 64000 => "3",
 02867                    < 96000 => "4",
 02868                    _ => "5"
 02869                };
 2870            }
 2871
 02872            if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase))
 2873            {
 2874                // lame's VBR is only good for a certain bitrate range
 2875                // For very low and very high bitrate, use abr mode
 02876                if (bitratePerChannel is < 122500 and > 48000)
 2877                {
 02878                    return " -qscale:a " + bitratePerChannel switch
 02879                    {
 02880                        < 64000 => "6",
 02881                        < 88000 => "4",
 02882                        < 112000 => "2",
 02883                        _ => "0"
 02884                    };
 2885                }
 2886
 02887                return " -abr:a 1" + " -b:a " + bitrate;
 2888            }
 2889
 02890            if (string.Equals(encoder, "aac_at", StringComparison.OrdinalIgnoreCase))
 2891            {
 2892                // aac_at's CVBR mode
 02893                return " -aac_at_mode:a 2" + " -b:a " + bitrate;
 2894            }
 2895
 02896            if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase))
 2897            {
 02898                return " -qscale:a " + bitratePerChannel switch
 02899                {
 02900                    < 40000 => "0",
 02901                    < 56000 => "2",
 02902                    < 80000 => "4",
 02903                    < 112000 => "6",
 02904                    _ => "8"
 02905                };
 2906            }
 2907
 02908            return null;
 2909        }
 2910
 2911        public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
 2912        {
 02913            var channels = state.OutputAudioChannels;
 2914
 02915            var filters = new List<string>();
 2916
 02917            if (channels is 2 && state.AudioStream?.Channels is > 2)
 2918            {
 02919                var hasDownMixFilter = DownMixAlgorithmsHelper.AlgorithmFilterStrings.TryGetValue((encodingOptions.DownM
 02920                if (hasDownMixFilter)
 2921                {
 02922                    filters.Add(downMixFilterString);
 2923                }
 2924
 02925                if (!encodingOptions.DownMixAudioBoost.Equals(1))
 2926                {
 02927                    filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
 2928                }
 2929            }
 2930
 02931            var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
 02932            if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && ShouldEncodeSubtitle(st
 2933            {
 02934                var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds;
 2935
 02936                filters.Add(
 02937                    string.Format(
 02938                        CultureInfo.InvariantCulture,
 02939                        "asetpts=PTS-{0}/TB",
 02940                        Math.Round(seconds)));
 2941            }
 2942
 02943            if (filters.Count > 0)
 2944            {
 02945                return " -af \"" + string.Join(',', filters) + "\"";
 2946            }
 2947
 02948            return string.Empty;
 2949        }
 2950
 2951        /// <summary>
 2952        /// Gets the number of audio channels to specify on the command line.
 2953        /// </summary>
 2954        /// <param name="state">The state.</param>
 2955        /// <param name="audioStream">The audio stream.</param>
 2956        /// <param name="outputAudioCodec">The output audio codec.</param>
 2957        /// <returns>System.Nullable{System.Int32}.</returns>
 2958        public int? GetNumAudioChannelsParam(EncodingJobInfo state, MediaStream audioStream, string outputAudioCodec)
 2959        {
 02960            if (audioStream is null)
 2961            {
 02962                return null;
 2963            }
 2964
 02965            var request = state.BaseRequest;
 2966
 02967            var codec = outputAudioCodec ?? string.Empty;
 2968
 02969            int? resultChannels = state.GetRequestedAudioChannels(codec);
 2970
 02971            var inputChannels = audioStream.Channels;
 2972
 02973            if (inputChannels > 0)
 2974            {
 02975                resultChannels = inputChannels < resultChannels ? inputChannels : resultChannels ?? inputChannels;
 2976            }
 2977
 02978            var isTranscodingAudio = !IsCopyCodec(codec);
 2979
 02980            if (isTranscodingAudio)
 2981            {
 02982                var audioEncoder = GetAudioEncoder(state);
 02983                if (!_audioTranscodeChannelLookup.TryGetValue(audioEncoder, out var transcoderChannelLimit))
 2984                {
 2985                    // Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many c
 02986                    transcoderChannelLimit = 8;
 2987                }
 2988
 2989                // Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelL
 02990                resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? tr
 2991
 02992                if (request.TranscodingMaxAudioChannels < resultChannels)
 2993                {
 02994                    resultChannels = request.TranscodingMaxAudioChannels;
 2995                }
 2996
 2997                // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
 2998                // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_d
 02999                if (state.TranscodingType != TranscodingJobType.Progressive
 03000                    && ((resultChannels > 2 && resultChannels < 6) || resultChannels == 7))
 3001                {
 3002                    // We can let FFMpeg supply an extra LFE channel for 5ch and 7ch to make them 5.1 and 7.1
 03003                    if (resultChannels == 5)
 3004                    {
 03005                        resultChannels = 6;
 3006                    }
 03007                    else if (resultChannels == 7)
 3008                    {
 03009                        resultChannels = 8;
 3010                    }
 3011                    else
 3012                    {
 3013                        // For other weird layout, just downmix to stereo for compatibility
 03014                        resultChannels = 2;
 3015                    }
 3016                }
 3017            }
 3018
 03019            return resultChannels;
 3020        }
 3021
 3022        /// <summary>
 3023        /// Enforces the resolution limit.
 3024        /// </summary>
 3025        /// <param name="state">The state.</param>
 3026        public void EnforceResolutionLimit(EncodingJobInfo state)
 3027        {
 03028            var videoRequest = state.BaseRequest;
 3029
 3030            // Switch the incoming params to be ceilings rather than fixed values
 03031            videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
 03032            videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
 3033
 03034            videoRequest.Width = null;
 03035            videoRequest.Height = null;
 03036        }
 3037
 3038        /// <summary>
 3039        /// Gets the fast seek command line parameter.
 3040        /// </summary>
 3041        /// <param name="state">The state.</param>
 3042        /// <param name="options">The options.</param>
 3043        /// <param name="segmentContainer">Segment Container.</param>
 3044        /// <returns>System.String.</returns>
 3045        /// <value>The fast seek command line parameter.</value>
 3046        public string GetFastSeekCommandLineParameter(EncodingJobInfo state, EncodingOptions options, string segmentCont
 3047        {
 43048            var time = state.BaseRequest.StartTimeTicks ?? 0;
 43049            var maxTime = state.RunTimeTicks ?? 0;
 43050            var seekParam = string.Empty;
 3051
 43052            if (time > 0)
 3053            {
 3054                // For direct streaming/remuxing, HLS segments start at keyframes.
 3055                // However, ffmpeg will seek to previous keyframe when the exact frame time is the input
 3056                // Workaround this by adding 0.5s offset to the seeking time to get the exact keyframe on most videos.
 3057                // This will help subtitle syncing.
 03058                var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCod
 03059                var seekTick = isHlsRemuxing ? time + 5000000L : time;
 3060
 3061                // Seeking beyond EOF makes no sense in transcoding. Clamp the seekTick value to
 3062                // [0, RuntimeTicks - 5.0s], so that the muxer gets packets and avoid error codes.
 03063                if (maxTime > 0)
 3064                {
 03065                    seekTick = Math.Clamp(seekTick, 0, Math.Max(maxTime - 50000000L, 0));
 3066                }
 3067
 03068                seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(seekT
 3069            }
 3070
 43071            return seekParam;
 3072        }
 3073
 3074        /// <summary>
 3075        /// Gets the map args.
 3076        /// </summary>
 3077        /// <param name="state">The state.</param>
 3078        /// <returns>System.String.</returns>
 3079        public string GetMapArgs(EncodingJobInfo state)
 3080        {
 3081            // If we don't have known media info
 3082            // If input is video, use -sn to drop subtitles
 3083            // Otherwise just return empty
 73084            if (state.VideoStream is null && state.AudioStream is null)
 3085            {
 03086                return state.IsInputVideo ? "-sn" : string.Empty;
 3087            }
 3088
 3089            // We have media info, but we don't know the stream index
 73090            if (state.VideoStream is not null && state.VideoStream.Index == -1)
 3091            {
 03092                return "-sn";
 3093            }
 3094
 3095            // We have media info, but we don't know the stream index
 73096            if (state.AudioStream is not null && state.AudioStream.Index == -1)
 3097            {
 03098                return state.IsInputVideo ? "-sn" : string.Empty;
 3099            }
 3100
 73101            var args = string.Empty;
 3102
 73103            if (state.VideoStream is not null)
 3104            {
 73105                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 3106
 73107                args += string.Format(
 73108                    CultureInfo.InvariantCulture,
 73109                    "-map 0:{0}",
 73110                    videoStreamIndex);
 3111            }
 3112            else
 3113            {
 3114                // No known video stream
 03115                args += "-vn";
 3116            }
 3117
 73118            if (state.AudioStream is not null)
 3119            {
 73120                int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
 73121                if (state.AudioStream.IsExternal)
 3122                {
 03123                    bool hasExternalSubAsInput = NeedsExternalSubtitleMuxing(state);
 03124                    int externalAudioMapIndex = hasExternalSubAsInput ? 2 : 1;
 3125
 03126                    args += string.Format(
 03127                        CultureInfo.InvariantCulture,
 03128                        " -map {0}:{1}",
 03129                        externalAudioMapIndex,
 03130                        audioStreamIndex);
 3131                }
 3132                else
 3133                {
 73134                    args += string.Format(
 73135                        CultureInfo.InvariantCulture,
 73136                        " -map 0:{0}",
 73137                        audioStreamIndex);
 3138                }
 3139            }
 3140            else
 3141            {
 03142                args += " -map -0:a";
 3143            }
 3144
 73145            var subtitleMethod = state.SubtitleDeliveryMethod;
 73146            if (state.SubtitleStream is null || subtitleMethod == SubtitleDeliveryMethod.Hls)
 3147            {
 13148                args += " -map -0:s";
 3149            }
 63150            else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
 3151            {
 63152                if (state.SubtitleStream.IsExternal)
 3153                {
 3154                    // External subtitle file is added as second FFmpeg input.
 3155                    // For single-stream files (SRT/ASS/VTT) the in-file index is always 0.
 3156                    // For multi-stream containers (MKS) we count how many streams from
 3157                    // the same file appear before the selected one.
 43158                    var inFileIndex = state.MediaSource.MediaStreams
 43159                        .Where(s => string.Equals(s.Path, state.SubtitleStream.Path, StringComparison.Ordinal))
 43160                        .TakeWhile(s => s.Index != state.SubtitleStream.Index)
 43161                        .Count();
 3162
 43163                    args += string.Format(
 43164                        CultureInfo.InvariantCulture,
 43165                        " -map 1:{0}",
 43166                        inFileIndex);
 3167                }
 3168                else
 3169                {
 23170                    int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3171
 23172                    args += string.Format(
 23173                        CultureInfo.InvariantCulture,
 23174                        " -map 0:{0}",
 23175                        subtitleStreamIndex);
 3176                }
 3177            }
 03178            else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
 3179            {
 03180                int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3181
 03182                args += string.Format(
 03183                    CultureInfo.InvariantCulture,
 03184                    " -map 1:{0} -sn",
 03185                    externalSubtitleStreamIndex);
 3186            }
 3187
 73188            return args;
 3189        }
 3190
 3191        /// <summary>
 3192        /// Gets the negative map args by filters.
 3193        /// </summary>
 3194        /// <param name="state">The state.</param>
 3195        /// <param name="videoProcessFilters">The videoProcessFilters.</param>
 3196        /// <returns>System.String.</returns>
 3197        public string GetNegativeMapArgsByFilters(EncodingJobInfo state, string videoProcessFilters)
 3198        {
 03199            string args = string.Empty;
 3200
 3201            // http://ffmpeg.org/ffmpeg-all.html#toc-Complex-filtergraphs-1
 03202            if (state.VideoStream is not null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordina
 3203            {
 03204                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 3205
 03206                args += string.Format(
 03207                    CultureInfo.InvariantCulture,
 03208                    "-map -0:{0} ",
 03209                    videoStreamIndex);
 3210            }
 3211
 03212            return args;
 3213        }
 3214
 3215        /// <summary>
 3216        /// Determines which stream will be used for playback.
 3217        /// </summary>
 3218        /// <param name="allStream">All stream.</param>
 3219        /// <param name="desiredIndex">Index of the desired.</param>
 3220        /// <param name="type">The type.</param>
 3221        /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
 3222        /// <returns>MediaStream.</returns>
 3223        public MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, b
 3224        {
 03225            var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
 3226
 03227            if (desiredIndex.HasValue)
 3228            {
 03229                var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
 3230
 03231                if (stream is not null)
 3232                {
 03233                    return stream;
 3234                }
 3235            }
 3236
 03237            if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
 3238            {
 03239                return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
 03240                       streams.FirstOrDefault();
 3241            }
 3242
 3243            // Just return the first one
 03244            return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
 3245        }
 3246
 3247        public static (int? Width, int? Height) GetFixedOutputSize(
 3248            int? videoWidth,
 3249            int? videoHeight,
 3250            int? requestedWidth,
 3251            int? requestedHeight,
 3252            int? requestedMaxWidth,
 3253            int? requestedMaxHeight)
 3254        {
 03255            if (!videoWidth.HasValue && !requestedWidth.HasValue)
 3256            {
 03257                return (null, null);
 3258            }
 3259
 03260            if (!videoHeight.HasValue && !requestedHeight.HasValue)
 3261            {
 03262                return (null, null);
 3263            }
 3264
 03265            int inputWidth = Convert.ToInt32(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture);
 03266            int inputHeight = Convert.ToInt32(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture);
 03267            int outputWidth = requestedWidth ?? inputWidth;
 03268            int outputHeight = requestedHeight ?? inputHeight;
 3269
 3270            // Don't transcode video to bigger than 4k when using HW.
 03271            int maximumWidth = Math.Min(requestedMaxWidth ?? outputWidth, 4096);
 03272            int maximumHeight = Math.Min(requestedMaxHeight ?? outputHeight, 4096);
 3273
 03274            if (outputWidth > maximumWidth || outputHeight > maximumHeight)
 3275            {
 03276                var scaleW = (double)maximumWidth / outputWidth;
 03277                var scaleH = (double)maximumHeight / outputHeight;
 03278                var scale = Math.Min(scaleW, scaleH);
 03279                outputWidth = Math.Min(maximumWidth, Convert.ToInt32(outputWidth * scale));
 03280                outputHeight = Math.Min(maximumHeight, Convert.ToInt32(outputHeight * scale));
 3281            }
 3282
 03283            outputWidth = 2 * (outputWidth / 2);
 03284            outputHeight = 2 * (outputHeight / 2);
 3285
 03286            return (outputWidth, outputHeight);
 3287        }
 3288
 3289        public static bool IsScaleRatioSupported(
 3290            int? videoWidth,
 3291            int? videoHeight,
 3292            int? requestedWidth,
 3293            int? requestedHeight,
 3294            int? requestedMaxWidth,
 3295            int? requestedMaxHeight,
 3296            double? maxScaleRatio)
 3297        {
 03298            var (outWidth, outHeight) = GetFixedOutputSize(
 03299                videoWidth,
 03300                videoHeight,
 03301                requestedWidth,
 03302                requestedHeight,
 03303                requestedMaxWidth,
 03304                requestedMaxHeight);
 3305
 03306            if (!videoWidth.HasValue
 03307                 || !videoHeight.HasValue
 03308                 || !outWidth.HasValue
 03309                 || !outHeight.HasValue
 03310                 || !maxScaleRatio.HasValue
 03311                 || (maxScaleRatio.Value < 1.0f))
 3312            {
 03313                return false;
 3314            }
 3315
 03316            var minScaleRatio = 1.0f / maxScaleRatio;
 03317            var scaleRatioW = (double)outWidth / (double)videoWidth;
 03318            var scaleRatioH = (double)outHeight / (double)videoHeight;
 3319
 03320            if (scaleRatioW < minScaleRatio
 03321                || scaleRatioW > maxScaleRatio
 03322                || scaleRatioH < minScaleRatio
 03323                || scaleRatioH > maxScaleRatio)
 3324            {
 03325                return false;
 3326            }
 3327
 03328            return true;
 3329        }
 3330
 3331        public static string GetHwScaleFilter(
 3332            string hwScalePrefix,
 3333            string hwScaleSuffix,
 3334            string videoFormat,
 3335            bool swapOutputWandH,
 3336            int? videoWidth,
 3337            int? videoHeight,
 3338            int? requestedWidth,
 3339            int? requestedHeight,
 3340            int? requestedMaxWidth,
 3341            int? requestedMaxHeight)
 3342        {
 03343            var (outWidth, outHeight) = GetFixedOutputSize(
 03344                videoWidth,
 03345                videoHeight,
 03346                requestedWidth,
 03347                requestedHeight,
 03348                requestedMaxWidth,
 03349                requestedMaxHeight);
 3350
 03351            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03352            var isSizeFixed = !videoWidth.HasValue
 03353                || outWidth.Value != videoWidth.Value
 03354                || !videoHeight.HasValue
 03355                || outHeight.Value != videoHeight.Value;
 3356
 03357            var swpOutW = swapOutputWandH ? outHeight.Value : outWidth.Value;
 03358            var swpOutH = swapOutputWandH ? outWidth.Value : outHeight.Value;
 3359
 03360            var arg1 = isSizeFixed ? $"=w={swpOutW}:h={swpOutH}" : string.Empty;
 03361            var arg2 = isFormatFixed ? $"format={videoFormat}" : string.Empty;
 03362            if (isFormatFixed)
 3363            {
 03364                arg2 = (isSizeFixed ? ':' : '=') + arg2;
 3365            }
 3366
 03367            if (!string.IsNullOrEmpty(hwScaleSuffix) && (isSizeFixed || isFormatFixed))
 3368            {
 03369                return string.Format(
 03370                    CultureInfo.InvariantCulture,
 03371                    "{0}_{1}{2}{3}",
 03372                    hwScalePrefix ?? "scale",
 03373                    hwScaleSuffix,
 03374                    arg1,
 03375                    arg2);
 3376            }
 3377
 03378            return string.Empty;
 3379        }
 3380
 3381        public static string GetGraphicalSubPreProcessFilters(
 3382            int? videoWidth,
 3383            int? videoHeight,
 3384            int? subtitleWidth,
 3385            int? subtitleHeight,
 3386            int? requestedWidth,
 3387            int? requestedHeight,
 3388            int? requestedMaxWidth,
 3389            int? requestedMaxHeight)
 3390        {
 03391            var (outWidth, outHeight) = GetFixedOutputSize(
 03392                videoWidth,
 03393                videoHeight,
 03394                requestedWidth,
 03395                requestedHeight,
 03396                requestedMaxWidth,
 03397                requestedMaxHeight);
 3398
 03399            if (!outWidth.HasValue
 03400                || !outHeight.HasValue
 03401                || outWidth.Value <= 0
 03402                || outHeight.Value <= 0)
 3403            {
 03404                return string.Empty;
 3405            }
 3406
 3407            // Automatically add padding based on subtitle input
 03408            var filters = @"scale,scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:blac
 3409
 03410            if (subtitleWidth.HasValue
 03411                && subtitleHeight.HasValue
 03412                && subtitleWidth.Value > 0
 03413                && subtitleHeight.Value > 0)
 3414            {
 03415                var videoDar = (double)outWidth.Value / outHeight.Value;
 03416                var subtitleDar = (double)subtitleWidth.Value / subtitleHeight.Value;
 3417
 3418                // No need to add padding when DAR is the same -> 1080p PGSSUB on 2160p video
 03419                if (Math.Abs(videoDar - subtitleDar) < 0.01f)
 3420                {
 03421                    filters = @"scale,scale={0}:{1}:fast_bilinear";
 3422                }
 3423            }
 3424
 03425            return string.Format(
 03426                CultureInfo.InvariantCulture,
 03427                filters,
 03428                outWidth.Value,
 03429                outHeight.Value);
 3430        }
 3431
 3432        public static string GetAlphaSrcFilter(
 3433            EncodingJobInfo state,
 3434            int? videoWidth,
 3435            int? videoHeight,
 3436            int? requestedWidth,
 3437            int? requestedHeight,
 3438            int? requestedMaxWidth,
 3439            int? requestedMaxHeight,
 3440            float? framerate)
 3441        {
 03442            var reqTicks = state.BaseRequest.StartTimeTicks ?? 0;
 03443            var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture
 03444            var (outWidth, outHeight) = GetFixedOutputSize(
 03445                videoWidth,
 03446                videoHeight,
 03447                requestedWidth,
 03448                requestedHeight,
 03449                requestedMaxWidth,
 03450                requestedMaxHeight);
 3451
 03452            if (outWidth.HasValue && outHeight.HasValue)
 3453            {
 03454                return string.Format(
 03455                    CultureInfo.InvariantCulture,
 03456                    "alphasrc=s={0}x{1}:r={2}:start='{3}'",
 03457                    outWidth.Value,
 03458                    outHeight.Value,
 03459                    framerate ?? 25,
 03460                    reqTicks > 0 ? startTime : 0);
 3461            }
 3462
 03463            return string.Empty;
 3464        }
 3465
 3466        public static string GetSwScaleFilter(
 3467            EncodingJobInfo state,
 3468            EncodingOptions options,
 3469            string videoEncoder,
 3470            int? videoWidth,
 3471            int? videoHeight,
 3472            Video3DFormat? threedFormat,
 3473            int? requestedWidth,
 3474            int? requestedHeight,
 3475            int? requestedMaxWidth,
 3476            int? requestedMaxHeight)
 3477        {
 03478            var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 03479            var isMjpeg = videoEncoder is not null && videoEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase)
 03480            var scaleVal = isV4l2 ? 64 : 2;
 03481            var targetAr = isMjpeg ? "(a*sar)" : "a"; // manually calculate AR when using mjpeg encoder
 3482
 3483            // If fixed dimensions were supplied
 03484            if (requestedWidth.HasValue && requestedHeight.HasValue)
 3485            {
 03486                if (isV4l2)
 3487                {
 03488                    var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 03489                    var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3490
 03491                    return string.Format(
 03492                            CultureInfo.InvariantCulture,
 03493                            "scale=trunc({0}/64)*64:trunc({1}/2)*2",
 03494                            widthParam,
 03495                            heightParam);
 3496                }
 3497
 03498                return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
 3499            }
 3500
 3501            // If Max dimensions were supplied, for width selects lowest even number between input width and width req s
 3502
 03503            if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
 3504            {
 03505                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 03506                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3507
 03508                return string.Format(
 03509                    CultureInfo.InvariantCulture,
 03510                    @"scale=trunc(min(max(iw\,ih*{3})\,min({0}\,{1}*{3}))/{2})*{2}:trunc(min(max(iw/{3}\,ih)\,min({0}/{3
 03511                    maxWidthParam,
 03512                    maxHeightParam,
 03513                    scaleVal,
 03514                    targetAr);
 3515            }
 3516
 3517            // If a fixed width was requested
 03518            if (requestedWidth.HasValue)
 3519            {
 03520                if (threedFormat.HasValue)
 3521                {
 3522                    // This method can handle 0 being passed in for the requested height
 03523                    return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0);
 3524                }
 3525
 03526                var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 3527
 03528                return string.Format(
 03529                    CultureInfo.InvariantCulture,
 03530                    "scale={0}:trunc(ow/{1}/2)*2",
 03531                    widthParam,
 03532                    targetAr);
 3533            }
 3534
 3535            // If a fixed height was requested
 03536            if (requestedHeight.HasValue)
 3537            {
 03538                var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3539
 03540                return string.Format(
 03541                    CultureInfo.InvariantCulture,
 03542                    "scale=trunc(oh*{2}/{1})*{1}:{0}",
 03543                    heightParam,
 03544                    scaleVal,
 03545                    targetAr);
 3546            }
 3547
 3548            // If a max width was requested
 03549            if (requestedMaxWidth.HasValue)
 3550            {
 03551                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 3552
 03553                return string.Format(
 03554                    CultureInfo.InvariantCulture,
 03555                    @"scale=trunc(min(max(iw\,ih*{2})\,{0})/{1})*{1}:trunc(ow/{2}/2)*2",
 03556                    maxWidthParam,
 03557                    scaleVal,
 03558                    targetAr);
 3559            }
 3560
 3561            // If a max height was requested
 03562            if (requestedMaxHeight.HasValue)
 3563            {
 03564                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3565
 03566                return string.Format(
 03567                    CultureInfo.InvariantCulture,
 03568                    @"scale=trunc(oh*{2}/{1})*{1}:min(max(iw/{2}\,ih)\,{0})",
 03569                    maxHeightParam,
 03570                    scaleVal,
 03571                    targetAr);
 3572            }
 3573
 03574            return string.Empty;
 3575        }
 3576
 3577        private static string GetFixedSwScaleFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight
 3578        {
 03579            var widthParam = requestedWidth.ToString(CultureInfo.InvariantCulture);
 03580            var heightParam = requestedHeight.ToString(CultureInfo.InvariantCulture);
 3581
 03582            string filter = null;
 3583
 03584            if (threedFormat.HasValue)
 3585            {
 03586                switch (threedFormat.Value)
 3587                {
 3588                    case Video3DFormat.HalfSideBySide:
 03589                        filter = @"crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(i
 3590                        // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars
 03591                        break;
 3592                    case Video3DFormat.FullSideBySide:
 03593                        filter = @"crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3594                        // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the s
 03595                        break;
 3596                    case Video3DFormat.HalfTopAndBottom:
 03597                        filter = @"crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(
 3598                        // htab crop height in half,scale to correct size, set the display aspect,crop out any black bar
 03599                        break;
 3600                    case Video3DFormat.FullTopAndBottom:
 03601                        filter = @"crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3602                        // ftab crop height in half, set the display aspect,crop out any black bars we may have made the
 3603                        break;
 3604                    default:
 3605                        break;
 3606                }
 3607            }
 3608
 3609            // default
 03610            if (filter is null)
 3611            {
 03612                if (requestedHeight > 0)
 3613                {
 03614                    filter = "scale=trunc({0}/2)*2:trunc({1}/2)*2";
 3615                }
 3616                else
 3617                {
 03618                    filter = "scale={0}:trunc({0}/a/2)*2";
 3619                }
 3620            }
 3621
 03622            return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam);
 3623        }
 3624
 3625        public static string GetSwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options)
 3626        {
 03627            var doubleRateDeint = options.DeinterlaceDoubleRate && state.VideoStream?.ReferenceFrameRate <= 30;
 03628            return string.Format(
 03629                CultureInfo.InvariantCulture,
 03630                "{0}={1}:-1:0",
 03631                options.DeinterlaceMethod.ToString().ToLowerInvariant(),
 03632                doubleRateDeint ? "1" : "0");
 3633        }
 3634
 3635        public string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix)
 3636        {
 03637            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03638            if (hwDeintSuffix.Contains("cuda", StringComparison.OrdinalIgnoreCase))
 3639            {
 03640                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3641
 03642                return string.Format(
 03643                    CultureInfo.InvariantCulture,
 03644                    "{0}_cuda={1}:-1:0",
 03645                    useBwdif ? "bwdif" : "yadif",
 03646                    doubleRateDeint ? "1" : "0");
 3647            }
 3648
 03649            if (hwDeintSuffix.Contains("opencl", StringComparison.OrdinalIgnoreCase))
 3650            {
 03651                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif;
 3652
 03653                if (_mediaEncoder.SupportsFilter("yadif_opencl")
 03654                    && _mediaEncoder.SupportsFilter("bwdif_opencl"))
 3655                {
 03656                    return string.Format(
 03657                        CultureInfo.InvariantCulture,
 03658                        "{0}_opencl={1}:-1:0",
 03659                        useBwdif ? "bwdif" : "yadif",
 03660                        doubleRateDeint ? "1" : "0");
 3661                }
 3662            }
 3663
 03664            if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
 3665            {
 03666                return string.Format(
 03667                    CultureInfo.InvariantCulture,
 03668                    "deinterlace_vaapi=rate={0}",
 03669                    doubleRateDeint ? "field" : "frame");
 3670            }
 3671
 03672            if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
 3673            {
 03674                return "deinterlace_qsv=mode=2";
 3675            }
 3676
 03677            if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
 3678            {
 03679                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3680
 03681                return string.Format(
 03682                    CultureInfo.InvariantCulture,
 03683                    "{0}_videotoolbox={1}:-1:0",
 03684                    useBwdif ? "bwdif" : "yadif",
 03685                    doubleRateDeint ? "1" : "0");
 3686            }
 3687
 03688            return string.Empty;
 3689        }
 3690
 3691        private string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat, bool forc
 3692        {
 03693            if (string.IsNullOrEmpty(hwTonemapSuffix))
 3694            {
 03695                return string.Empty;
 3696            }
 3697
 03698            var args = string.Empty;
 03699            var algorithm = options.TonemappingAlgorithm.ToString().ToLowerInvariant();
 03700            var mode = options.TonemappingMode.ToString().ToLowerInvariant();
 03701            var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 03702            var rangeString = range.ToString().ToLowerInvariant();
 3703
 03704            if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
 3705            {
 03706                var doVaVppProcamp = false;
 03707                var procampParams = string.Empty;
 03708                if (options.VppTonemappingBrightness != 0
 03709                    && options.VppTonemappingBrightness >= -100
 03710                    && options.VppTonemappingBrightness <= 100)
 3711                {
 03712                    procampParams += "procamp_vaapi=b={0}";
 03713                    doVaVppProcamp = true;
 3714                }
 3715
 03716                if (options.VppTonemappingContrast > 1
 03717                    && options.VppTonemappingContrast <= 10)
 3718                {
 03719                    procampParams += doVaVppProcamp ? ":c={1}" : "procamp_vaapi=c={1}";
 03720                    doVaVppProcamp = true;
 3721                }
 3722
 03723                args = procampParams + "{2}tonemap_vaapi=format={3}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
 3724
 03725                return string.Format(
 03726                        CultureInfo.InvariantCulture,
 03727                        args,
 03728                        options.VppTonemappingBrightness,
 03729                        options.VppTonemappingContrast,
 03730                        doVaVppProcamp ? "," : string.Empty,
 03731                        videoFormat ?? "nv12");
 3732            }
 3733            else
 3734            {
 03735                args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
 3736
 03737                var useLegacyTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode
 03738                                           && _legacyTonemapModes.Contains(options.TonemappingMode);
 3739
 03740                var useAdvancedTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegAdvancedTonemapMode
 03741                                              && _advancedTonemapModes.Contains(options.TonemappingMode);
 3742
 03743                if (useLegacyTonemapModes || useAdvancedTonemapModes)
 3744                {
 03745                    args += ":tonemap_mode={5}";
 3746                }
 3747
 03748                if (options.TonemappingParam != 0)
 3749                {
 03750                    args += ":param={6}";
 3751                }
 3752
 03753                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3754                {
 03755                    args += ":range={7}";
 3756                }
 3757            }
 3758
 03759            return string.Format(
 03760                    CultureInfo.InvariantCulture,
 03761                    args,
 03762                    hwTonemapSuffix,
 03763                    videoFormat ?? "nv12",
 03764                    algorithm,
 03765                    options.TonemappingPeak,
 03766                    options.TonemappingDesat,
 03767                    mode,
 03768                    options.TonemappingParam,
 03769                    rangeString);
 3770        }
 3771
 3772        private string GetLibplaceboFilter(
 3773            EncodingOptions options,
 3774            string videoFormat,
 3775            bool doTonemap,
 3776            int? videoWidth,
 3777            int? videoHeight,
 3778            int? requestedWidth,
 3779            int? requestedHeight,
 3780            int? requestedMaxWidth,
 3781            int? requestedMaxHeight,
 3782            bool forceFullRange)
 3783        {
 03784            var (outWidth, outHeight) = GetFixedOutputSize(
 03785                videoWidth,
 03786                videoHeight,
 03787                requestedWidth,
 03788                requestedHeight,
 03789                requestedMaxWidth,
 03790                requestedMaxHeight);
 3791
 03792            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03793            var isSizeFixed = !videoWidth.HasValue
 03794                || outWidth.Value != videoWidth.Value
 03795                || !videoHeight.HasValue
 03796                || outHeight.Value != videoHeight.Value;
 3797
 03798            var sizeArg = isSizeFixed ? (":w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty;
 03799            var formatArg = isFormatFixed ? (":format=" + videoFormat) : string.Empty;
 03800            var tonemapArg = string.Empty;
 3801
 03802            if (doTonemap)
 3803            {
 03804                var algorithm = options.TonemappingAlgorithm;
 03805                var algorithmString = "clip";
 03806                var mode = options.TonemappingMode;
 03807                var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 3808
 03809                if (algorithm == TonemappingAlgorithm.bt2390)
 3810                {
 03811                    algorithmString = "bt.2390";
 3812                }
 03813                else if (algorithm != TonemappingAlgorithm.none)
 3814                {
 03815                    algorithmString = algorithm.ToString().ToLowerInvariant();
 3816                }
 3817
 03818                tonemapArg = $":tonemapping={algorithmString}:peak_detect=0:color_primaries=bt709:color_trc=bt709:colors
 3819
 03820                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3821                {
 03822                    tonemapArg += ":range=" + range.ToString().ToLowerInvariant();
 3823                }
 3824            }
 3825
 03826            return string.Format(
 03827                CultureInfo.InvariantCulture,
 03828                "libplacebo=upscaler=none:downscaler=none{0}{1}{2}",
 03829                sizeArg,
 03830                formatArg,
 03831                tonemapArg);
 3832        }
 3833
 3834        public string GetVideoTransposeDirection(EncodingJobInfo state)
 3835        {
 03836            return (state.VideoStream?.Rotation ?? 0) switch
 03837            {
 03838                90 => "cclock",
 03839                180 => "reversal",
 03840                -90 => "clock",
 03841                -180 => "reversal",
 03842                _ => string.Empty
 03843            };
 3844        }
 3845
 3846        /// <summary>
 3847        /// Gets the parameter of software filter chain.
 3848        /// </summary>
 3849        /// <param name="state">Encoding state.</param>
 3850        /// <param name="options">Encoding options.</param>
 3851        /// <param name="vidEncoder">Video encoder to use.</param>
 3852        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3853        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetSwVidFilterChain(
 3854            EncodingJobInfo state,
 3855            EncodingOptions options,
 3856            string vidEncoder)
 3857        {
 03858            var inW = state.VideoStream?.Width;
 03859            var inH = state.VideoStream?.Height;
 03860            var reqW = state.BaseRequest.Width;
 03861            var reqH = state.BaseRequest.Height;
 03862            var reqMaxW = state.BaseRequest.MaxWidth;
 03863            var reqMaxH = state.BaseRequest.MaxHeight;
 03864            var threeDFormat = state.MediaSource.Video3DFormat;
 3865
 03866            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03867            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03868            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 03869            var isV4l2Encoder = vidEncoder.Contains("h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 3870
 03871            var doDeintH2645 = IsDeinterlaceAvailable(state);
 03872            var doToneMap = IsSwTonemapAvailable(state, options);
 03873            var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI;
 3874
 03875            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03876            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03877            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 3878
 03879            var rotation = state.VideoStream?.Rotation ?? 0;
 03880            var swapWAndH = Math.Abs(rotation) == 90;
 03881            var swpInW = swapWAndH ? inH : inW;
 03882            var swpInH = swapWAndH ? inW : inH;
 3883
 3884            /* Make main filters for video stream */
 03885            var mainFilters = new List<string>();
 3886
 03887            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doToneMap));
 3888
 3889            // INPUT sw surface(memory/copy-back from vram)
 3890            // sw deint
 03891            if (doDeintH2645)
 3892            {
 03893                var deintFilter = GetSwDeinterlaceFilter(state, options);
 03894                mainFilters.Add(deintFilter);
 3895            }
 3896
 03897            var outFormat = isSwDecoder ? "yuv420p" : "nv12";
 03898            var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, r
 03899            if (isVaapiEncoder)
 3900            {
 03901                outFormat = "nv12";
 3902            }
 03903            else if (isV4l2Encoder)
 3904            {
 03905                outFormat = "yuv420p";
 3906            }
 3907
 3908            // sw scale
 03909            mainFilters.Add(swScaleFilter);
 3910
 3911            // sw tonemap
 03912            if (doToneMap)
 3913            {
 3914                // tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary
 03915                var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat;
 03916                var tonemapArgString = "tonemapx=tonemap={0}:desat={1}:peak={2}:t=bt709:m=bt709:p=bt709:format={3}";
 3917
 03918                if (options.TonemappingParam != 0)
 3919                {
 03920                    tonemapArgString += ":param={4}";
 3921                }
 3922
 03923                var range = options.TonemappingRange;
 03924                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3925                {
 03926                    tonemapArgString += ":range={5}";
 3927                }
 3928
 03929                var tonemapArgs = string.Format(
 03930                    CultureInfo.InvariantCulture,
 03931                    tonemapArgString,
 03932                    options.TonemappingAlgorithm,
 03933                    options.TonemappingDesat,
 03934                    options.TonemappingPeak,
 03935                    tonemapFormat,
 03936                    options.TonemappingParam,
 03937                    options.TonemappingRange);
 3938
 03939                mainFilters.Add(tonemapArgs);
 3940            }
 3941            else
 3942            {
 3943                // OUTPUT yuv420p/nv12 surface(memory)
 03944                mainFilters.Add("format=" + outFormat);
 3945            }
 3946
 3947            /* Make sub and overlay filters for subtitle stream */
 03948            var subFilters = new List<string>();
 03949            var overlayFilters = new List<string>();
 03950            if (hasTextSubs)
 3951            {
 3952                // subtitles=f='*.ass':alpha=0
 03953                var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03954                mainFilters.Add(textSubtitlesFilter);
 3955            }
 03956            else if (hasGraphicalSubs)
 3957            {
 03958                var subW = state.SubtitleStream?.Width;
 03959                var subH = state.SubtitleStream?.Height;
 03960                var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW
 03961                subFilters.Add(subPreProcFilters);
 03962                overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 3963            }
 3964
 03965            return (mainFilters, subFilters, overlayFilters);
 3966        }
 3967
 3968        /// <summary>
 3969        /// Gets the parameter of Nvidia NVENC filter chain.
 3970        /// </summary>
 3971        /// <param name="state">Encoding state.</param>
 3972        /// <param name="options">Encoding options.</param>
 3973        /// <param name="vidEncoder">Video encoder to use.</param>
 3974        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3975        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFilterChain(
 3976            EncodingJobInfo state,
 3977            EncodingOptions options,
 3978            string vidEncoder)
 3979        {
 03980            if (options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 3981            {
 03982                return (null, null, null);
 3983            }
 3984
 03985            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03986            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03987            var isSwEncoder = !vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 3988
 3989            // legacy cuvid pipeline(copy-back)
 03990            if ((isSwDecoder && isSwEncoder)
 03991                || !IsCudaFullSupported()
 03992                || !_mediaEncoder.SupportsFilter("alphasrc"))
 3993            {
 03994                return GetSwVidFilterChain(state, options, vidEncoder);
 3995            }
 3996
 3997            // preferred nvdec/cuvid + cuda filters + nvenc pipeline
 03998            return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 3999        }
 4000
 4001        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFiltersPrefe
 4002            EncodingJobInfo state,
 4003            EncodingOptions options,
 4004            string vidDecoder,
 4005            string vidEncoder)
 4006        {
 04007            var inW = state.VideoStream?.Width;
 04008            var inH = state.VideoStream?.Height;
 04009            var reqW = state.BaseRequest.Width;
 04010            var reqH = state.BaseRequest.Height;
 04011            var reqMaxW = state.BaseRequest.MaxWidth;
 04012            var reqMaxH = state.BaseRequest.MaxHeight;
 04013            var threeDFormat = state.MediaSource.Video3DFormat;
 4014
 04015            var isNvDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 04016            var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 04017            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04018            var isSwEncoder = !isNvencEncoder;
 04019            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04020            var isCuInCuOut = isNvDecoder && isNvencEncoder;
 4021
 04022            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 04023            var doDeintH2645 = IsDeinterlaceAvailable(state);
 04024            var doCuTonemap = IsHwTonemapAvailable(state, options);
 4025
 04026            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04027            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04028            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04029            var hasAssSubs = hasSubs
 04030                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04031                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04032            var subW = state.SubtitleStream?.Width;
 04033            var subH = state.SubtitleStream?.Height;
 4034
 04035            var rotation = state.VideoStream?.Rotation ?? 0;
 04036            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04037            var doCuTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_cuda");
 04038            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isNvDecoder && doCuTranspose));
 04039            var swpInW = swapWAndH ? inH : inW;
 04040            var swpInH = swapWAndH ? inW : inH;
 4041
 4042            /* Make main filters for video stream */
 04043            var mainFilters = new List<string>();
 4044
 04045            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doCuTonemap));
 4046
 04047            if (isSwDecoder)
 4048            {
 4049                // INPUT sw surface(memory)
 4050                // sw deint
 04051                if (doDeintH2645)
 4052                {
 04053                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04054                    mainFilters.Add(swDeintFilter);
 4055                }
 4056
 04057                var outFormat = doCuTonemap ? "yuv420p10le" : "yuv420p";
 04058                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 4059                // sw scale
 04060                mainFilters.Add(swScaleFilter);
 04061                mainFilters.Add($"format={outFormat}");
 4062
 4063                // sw => hw
 04064                if (doCuTonemap)
 4065                {
 04066                    mainFilters.Add("hwupload=derive_device=cuda");
 4067                }
 4068            }
 4069
 04070            if (isNvDecoder)
 4071            {
 4072                // INPUT cuda surface(vram)
 4073                // hw deint
 04074                if (doDeintH2645)
 4075                {
 04076                    var deintFilter = GetHwDeinterlaceFilter(state, options, "cuda");
 04077                    mainFilters.Add(deintFilter);
 4078                }
 4079
 4080                // hw transpose
 04081                if (doCuTranspose)
 4082                {
 04083                    mainFilters.Add($"transpose_cuda=dir={transposeDir}");
 4084                }
 4085
 04086                var isRext = IsVideoStreamHevcRext(state);
 04087                var outFormat = doCuTonemap ? (isRext ? "p010" : string.Empty) : "yuv420p";
 04088                var hwScaleFilter = GetHwScaleFilter("scale", "cuda", outFormat, false, swpInW, swpInH, reqW, reqH, reqM
 4089                // hw scale
 04090                mainFilters.Add(hwScaleFilter);
 4091            }
 4092
 4093            // hw tonemap
 04094            if (doCuTonemap)
 4095            {
 04096                var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p", isMjpegEncoder);
 04097                mainFilters.Add(tonemapFilter);
 4098            }
 4099
 04100            var memoryOutput = false;
 04101            var isUploadForCuTonemap = isSwDecoder && doCuTonemap;
 04102            if ((isNvDecoder && isSwEncoder) || (isUploadForCuTonemap && hasSubs))
 4103            {
 04104                memoryOutput = true;
 4105
 4106                // OUTPUT yuv420p surface(memory)
 04107                mainFilters.Add("hwdownload");
 04108                mainFilters.Add("format=yuv420p");
 4109            }
 4110
 4111            // OUTPUT yuv420p surface(memory)
 04112            if (isSwDecoder && isNvencEncoder && !isUploadForCuTonemap)
 4113            {
 04114                memoryOutput = true;
 4115            }
 4116
 04117            if (memoryOutput)
 4118            {
 4119                // text subtitles
 04120                if (hasTextSubs)
 4121                {
 04122                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04123                    mainFilters.Add(textSubtitlesFilter);
 4124                }
 4125            }
 4126
 4127            // OUTPUT cuda(yuv420p) surface(vram)
 4128
 4129            /* Make sub and overlay filters for subtitle stream */
 04130            var subFilters = new List<string>();
 04131            var overlayFilters = new List<string>();
 04132            if (isCuInCuOut)
 4133            {
 04134                if (hasSubs)
 4135                {
 04136                    var alphaFormatOpt = string.Empty;
 04137                    if (hasGraphicalSubs)
 4138                    {
 04139                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04140                        subFilters.Add(subPreProcFilters);
 04141                        subFilters.Add("format=yuva420p");
 4142                    }
 04143                    else if (hasTextSubs)
 4144                    {
 04145                        var framerate = state.VideoStream?.RealFrameRate;
 04146                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4147
 4148                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04149                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04150                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04151                        subFilters.Add(alphaSrcFilter);
 04152                        subFilters.Add("format=yuva420p");
 04153                        subFilters.Add(subTextSubtitlesFilter);
 4154
 04155                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayCudaAlphaFormat)
 04156                            ? ":alpha_format=premultiplied" : string.Empty;
 4157                    }
 4158
 04159                    subFilters.Add("hwupload=derive_device=cuda");
 04160                    overlayFilters.Add($"overlay_cuda=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 4161                }
 4162            }
 4163            else
 4164            {
 04165                if (hasGraphicalSubs)
 4166                {
 04167                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04168                    subFilters.Add(subPreProcFilters);
 04169                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4170                }
 4171            }
 4172
 04173            return (mainFilters, subFilters, overlayFilters);
 4174        }
 4175
 4176        /// <summary>
 4177        /// Gets the parameter of AMD AMF filter chain.
 4178        /// </summary>
 4179        /// <param name="state">Encoding state.</param>
 4180        /// <param name="options">Encoding options.</param>
 4181        /// <param name="vidEncoder">Video encoder to use.</param>
 4182        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4183        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVidFilterChain(
 4184            EncodingJobInfo state,
 4185            EncodingOptions options,
 4186            string vidEncoder)
 4187        {
 04188            if (options.HardwareAccelerationType != HardwareAccelerationType.amf)
 4189            {
 04190                return (null, null, null);
 4191            }
 4192
 04193            var isWindows = OperatingSystem.IsWindows();
 04194            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04195            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04196            var isSwEncoder = !vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04197            var isAmfDx11OclSupported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va") && IsOpenclFullSupported()
 4198
 4199            // legacy d3d11va pipeline(copy-back)
 04200            if ((isSwDecoder && isSwEncoder)
 04201                || !isAmfDx11OclSupported
 04202                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4203            {
 04204                return GetSwVidFilterChain(state, options, vidEncoder);
 4205            }
 4206
 4207            // preferred d3d11va + opencl filters + amf pipeline
 04208            return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4209        }
 4210
 4211        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdDx11VidFiltersPref
 4212            EncodingJobInfo state,
 4213            EncodingOptions options,
 4214            string vidDecoder,
 4215            string vidEncoder)
 4216        {
 04217            var inW = state.VideoStream?.Width;
 04218            var inH = state.VideoStream?.Height;
 04219            var reqW = state.BaseRequest.Width;
 04220            var reqH = state.BaseRequest.Height;
 04221            var reqMaxW = state.BaseRequest.MaxWidth;
 04222            var reqMaxH = state.BaseRequest.MaxHeight;
 04223            var threeDFormat = state.MediaSource.Video3DFormat;
 4224
 04225            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04226            var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04227            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04228            var isSwEncoder = !isAmfEncoder;
 04229            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04230            var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder;
 4231
 04232            var doDeintH2645 = IsDeinterlaceAvailable(state);
 04233            var doOclTonemap = IsHwTonemapAvailable(state, options);
 4234
 04235            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04236            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04237            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04238            var hasAssSubs = hasSubs
 04239                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04240                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04241            var subW = state.SubtitleStream?.Width;
 04242            var subH = state.SubtitleStream?.Height;
 4243
 04244            var rotation = state.VideoStream?.Rotation ?? 0;
 04245            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04246            var doOclTranspose = !string.IsNullOrEmpty(transposeDir)
 04247                && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TransposeOpenclReversal);
 04248            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isD3d11vaDecoder && doOclTranspose));
 04249            var swpInW = swapWAndH ? inH : inW;
 04250            var swpInH = swapWAndH ? inW : inH;
 4251
 4252            /* Make main filters for video stream */
 04253            var mainFilters = new List<string>();
 4254
 04255            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 4256
 04257            if (isSwDecoder)
 4258            {
 4259                // INPUT sw surface(memory)
 4260                // sw deint
 04261                if (doDeintH2645)
 4262                {
 04263                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04264                    mainFilters.Add(swDeintFilter);
 4265                }
 4266
 04267                var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
 04268                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 4269                // sw scale
 04270                mainFilters.Add(swScaleFilter);
 04271                mainFilters.Add($"format={outFormat}");
 4272
 4273                // keep video at memory except ocl tonemap,
 4274                // since the overhead caused by hwupload >>> using sw filter.
 4275                // sw => hw
 04276                if (doOclTonemap)
 4277                {
 04278                    mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=24");
 04279                    mainFilters.Add("format=d3d11");
 04280                    mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4281                }
 4282            }
 4283
 04284            if (isD3d11vaDecoder)
 4285            {
 4286                // INPUT d3d11 surface(vram)
 4287                // map from d3d11va to opencl via d3d11-opencl interop.
 04288                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4289
 4290                // hw deint
 04291                if (doDeintH2645)
 4292                {
 04293                    var deintFilter = GetHwDeinterlaceFilter(state, options, "opencl");
 04294                    mainFilters.Add(deintFilter);
 4295                }
 4296
 4297                // hw transpose
 04298                if (doOclTranspose)
 4299                {
 04300                    mainFilters.Add($"transpose_opencl=dir={transposeDir}");
 4301                }
 4302
 04303                var outFormat = doOclTonemap ? string.Empty : "nv12";
 04304                var hwScaleFilter = GetHwScaleFilter("scale", "opencl", outFormat, false, swpInW, swpInH, reqW, reqH, re
 4305                // hw scale
 04306                mainFilters.Add(hwScaleFilter);
 4307            }
 4308
 4309            // hw tonemap
 04310            if (doOclTonemap)
 4311            {
 04312                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04313                mainFilters.Add(tonemapFilter);
 4314            }
 4315
 04316            var memoryOutput = false;
 04317            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04318            if (isD3d11vaDecoder && isSwEncoder)
 4319            {
 04320                memoryOutput = true;
 4321
 4322                // OUTPUT nv12 surface(memory)
 4323                // prefer hwmap to hwdownload on opencl.
 04324                var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap=mode=read";
 04325                mainFilters.Add(hwTransferFilter);
 04326                mainFilters.Add("format=nv12");
 4327            }
 4328
 4329            // OUTPUT yuv420p surface
 04330            if (isSwDecoder && isAmfEncoder && !isUploadForOclTonemap)
 4331            {
 04332                memoryOutput = true;
 4333            }
 4334
 04335            if (memoryOutput)
 4336            {
 4337                // text subtitles
 04338                if (hasTextSubs)
 4339                {
 04340                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04341                    mainFilters.Add(textSubtitlesFilter);
 4342                }
 4343            }
 4344
 04345            if ((isDxInDxOut || isUploadForOclTonemap) && !hasSubs)
 4346            {
 4347                // OUTPUT d3d11(nv12) surface(vram)
 4348                // reverse-mapping via d3d11-opencl interop.
 04349                mainFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04350                mainFilters.Add("format=d3d11");
 4351            }
 4352
 4353            /* Make sub and overlay filters for subtitle stream */
 04354            var subFilters = new List<string>();
 04355            var overlayFilters = new List<string>();
 04356            if (isDxInDxOut || isUploadForOclTonemap)
 4357            {
 04358                if (hasSubs)
 4359                {
 04360                    var alphaFormatOpt = string.Empty;
 04361                    if (hasGraphicalSubs)
 4362                    {
 04363                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04364                        subFilters.Add(subPreProcFilters);
 04365                        subFilters.Add("format=yuva420p");
 4366                    }
 04367                    else if (hasTextSubs)
 4368                    {
 04369                        var framerate = state.VideoStream?.RealFrameRate;
 04370                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4371
 4372                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04373                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04374                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04375                        subFilters.Add(alphaSrcFilter);
 04376                        subFilters.Add("format=yuva420p");
 04377                        subFilters.Add(subTextSubtitlesFilter);
 4378
 04379                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclAlphaForma
 04380                            ? ":alpha_format=premultiplied" : string.Empty;
 4381                    }
 4382
 04383                    subFilters.Add("hwupload=derive_device=opencl");
 04384                    overlayFilters.Add($"overlay_opencl=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 04385                    overlayFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04386                    overlayFilters.Add("format=d3d11");
 4387                }
 4388            }
 04389            else if (memoryOutput)
 4390            {
 04391                if (hasGraphicalSubs)
 4392                {
 04393                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04394                    subFilters.Add(subPreProcFilters);
 04395                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4396                }
 4397            }
 4398
 04399            return (mainFilters, subFilters, overlayFilters);
 4400        }
 4401
 4402        /// <summary>
 4403        /// Gets the parameter of Intel QSV filter chain.
 4404        /// </summary>
 4405        /// <param name="state">Encoding state.</param>
 4406        /// <param name="options">Encoding options.</param>
 4407        /// <param name="vidEncoder">Video encoder to use.</param>
 4408        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4409        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVidFilterChain(
 4410            EncodingJobInfo state,
 4411            EncodingOptions options,
 4412            string vidEncoder)
 4413        {
 04414            if (options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 4415            {
 04416                return (null, null, null);
 4417            }
 4418
 04419            var isWindows = OperatingSystem.IsWindows();
 04420            var isLinux = OperatingSystem.IsLinux();
 04421            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04422            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04423            var isSwEncoder = !vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04424            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 04425            var isIntelDx11OclSupported = isWindows
 04426                && _mediaEncoder.SupportsHwaccel("d3d11va")
 04427                && isQsvOclSupported;
 04428            var isIntelVaapiOclSupported = isLinux
 04429                && IsVaapiSupported(state)
 04430                && isQsvOclSupported;
 4431
 4432            // legacy qsv pipeline(copy-back)
 04433            if ((isSwDecoder && isSwEncoder)
 04434                || (!isIntelVaapiOclSupported && !isIntelDx11OclSupported)
 04435                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4436            {
 04437                return GetSwVidFilterChain(state, options, vidEncoder);
 4438            }
 4439
 4440            // preferred qsv(vaapi) + opencl filters pipeline
 04441            if (isIntelVaapiOclSupported)
 4442            {
 04443                return GetIntelQsvVaapiVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4444            }
 4445
 4446            // preferred qsv(d3d11) + opencl filters pipeline
 04447            if (isIntelDx11OclSupported)
 4448            {
 04449                return GetIntelQsvDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4450            }
 4451
 04452            return (null, null, null);
 4453        }
 4454
 4455        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvDx11VidFilter
 4456            EncodingJobInfo state,
 4457            EncodingOptions options,
 4458            string vidDecoder,
 4459            string vidEncoder)
 4460        {
 04461            var inW = state.VideoStream?.Width;
 04462            var inH = state.VideoStream?.Height;
 04463            var reqW = state.BaseRequest.Width;
 04464            var reqH = state.BaseRequest.Height;
 04465            var reqMaxW = state.BaseRequest.MaxWidth;
 04466            var reqMaxH = state.BaseRequest.MaxHeight;
 04467            var threeDFormat = state.MediaSource.Video3DFormat;
 4468
 04469            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04470            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04471            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04472            var isHwDecoder = isD3d11vaDecoder || isQsvDecoder;
 04473            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04474            var isSwEncoder = !isQsvEncoder;
 04475            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04476            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4477
 04478            var doDeintH2645 = IsDeinterlaceAvailable(state);
 04479            var doVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04480            var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options);
 04481            var doTonemap = doVppTonemap || doOclTonemap;
 4482
 04483            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04484            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04485            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04486            var hasAssSubs = hasSubs
 04487                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04488                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04489            var subW = state.SubtitleStream?.Width;
 04490            var subH = state.SubtitleStream?.Height;
 4491
 04492            var rotation = state.VideoStream?.Rotation ?? 0;
 04493            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04494            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04495            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isD3d11vaDecoder || isQsvDecoder) && doVppTran
 04496            var swpInW = swapWAndH ? inH : inW;
 04497            var swpInH = swapWAndH ? inW : inH;
 4498
 4499            /* Make main filters for video stream */
 04500            var mainFilters = new List<string>();
 4501
 04502            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4503
 04504            if (isSwDecoder)
 4505            {
 4506                // INPUT sw surface(memory)
 4507                // sw deint
 04508                if (doDeintH2645)
 4509                {
 04510                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04511                    mainFilters.Add(swDeintFilter);
 4512                }
 4513
 04514                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04515                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04516                if (isMjpegEncoder && !doOclTonemap)
 4517                {
 4518                    // sw decoder + hw mjpeg encoder
 04519                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4520                }
 4521
 4522                // sw scale
 04523                mainFilters.Add(swScaleFilter);
 04524                mainFilters.Add($"format={outFormat}");
 4525
 4526                // keep video at memory except ocl tonemap,
 4527                // since the overhead caused by hwupload >>> using sw filter.
 4528                // sw => hw
 04529                if (doOclTonemap)
 4530                {
 04531                    mainFilters.Add("hwupload=derive_device=opencl");
 4532                }
 4533            }
 04534            else if (isD3d11vaDecoder || isQsvDecoder)
 4535            {
 04536                var isRext = IsVideoStreamHevcRext(state);
 04537                var twoPassVppTonemap = false;
 04538                var doVppFullRangeOut = isMjpegEncoder
 04539                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04540                var doVppScaleModeHq = isMjpegEncoder
 04541                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 04542                var doVppProcamp = false;
 04543                var procampParams = string.Empty;
 04544                var procampParamsString = string.Empty;
 04545                if (doVppTonemap)
 4546                {
 04547                    if (isRext)
 4548                    {
 4549                        // VPP tonemap requires p010 input
 04550                        twoPassVppTonemap = true;
 4551                    }
 4552
 04553                    if (options.VppTonemappingBrightness != 0
 04554                        && options.VppTonemappingBrightness >= -100
 04555                        && options.VppTonemappingBrightness <= 100)
 4556                    {
 04557                        procampParamsString += ":brightness={0}";
 04558                        twoPassVppTonemap = doVppProcamp = true;
 4559                    }
 4560
 04561                    if (options.VppTonemappingContrast > 1
 04562                        && options.VppTonemappingContrast <= 10)
 4563                    {
 04564                        procampParamsString += ":contrast={1}";
 04565                        twoPassVppTonemap = doVppProcamp = true;
 4566                    }
 4567
 04568                    if (doVppProcamp)
 4569                    {
 04570                        procampParamsString += ":procamp=1:async_depth=2";
 04571                        procampParams = string.Format(
 04572                            CultureInfo.InvariantCulture,
 04573                            procampParamsString,
 04574                            options.VppTonemappingBrightness,
 04575                            options.VppTonemappingContrast);
 4576                    }
 4577                }
 4578
 04579                var outFormat = doOclTonemap ? ((doVppTranspose || isRext) ? "p010" : string.Empty) : "nv12";
 04580                outFormat = twoPassVppTonemap ? "p010" : outFormat;
 4581
 04582                var swapOutputWandH = doVppTranspose && swapWAndH;
 04583                var hwScaleFilter = GetHwScaleFilter("vpp", "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, req
 4584
 4585                // d3d11va doesn't support dynamic pool size, use vpp filter ctx to relay
 4586                // to prevent encoder async and bframes from exhausting the decoder pool.
 04587                if (!string.IsNullOrEmpty(hwScaleFilter) && isD3d11vaDecoder)
 4588                {
 04589                    hwScaleFilter += ":passthrough=0";
 4590                }
 4591
 04592                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose)
 4593                {
 04594                    hwScaleFilter += $":transpose={transposeDir}";
 4595                }
 4596
 04597                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4598                {
 04599                    hwScaleFilter += (doVppFullRangeOut && !doOclTonemap) ? ":out_range=pc" : string.Empty;
 04600                    hwScaleFilter += doVppScaleModeHq ? ":scale_mode=hq" : string.Empty;
 4601                }
 4602
 04603                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTonemap)
 4604                {
 04605                    hwScaleFilter += doVppProcamp ? procampParams : (twoPassVppTonemap ? string.Empty : ":tonemap=1");
 4606                }
 4607
 04608                if (isD3d11vaDecoder)
 4609                {
 04610                    if (!string.IsNullOrEmpty(hwScaleFilter) || doDeintH2645)
 4611                    {
 4612                        // INPUT d3d11 surface(vram)
 4613                        // map from d3d11va to qsv.
 04614                        mainFilters.Add("hwmap=derive_device=qsv");
 4615                    }
 4616                }
 4617
 4618                // hw deint
 04619                if (doDeintH2645)
 4620                {
 04621                    var deintFilter = GetHwDeinterlaceFilter(state, options, "qsv");
 04622                    mainFilters.Add(deintFilter);
 4623                }
 4624
 4625                // hw transpose & scale & tonemap(w/o procamp)
 04626                mainFilters.Add(hwScaleFilter);
 4627
 4628                // hw tonemap(w/ procamp)
 04629                if (doVppTonemap && twoPassVppTonemap)
 4630                {
 04631                    mainFilters.Add("vpp_qsv=tonemap=1:format=nv12:async_depth=2");
 4632                }
 4633
 4634                // force bt709 just in case vpp tonemap is not triggered or using MSDK instead of VPL.
 04635                if (doVppTonemap)
 4636                {
 04637                    mainFilters.Add(GetOverwriteColorPropertiesParam(state, false));
 4638                }
 4639            }
 4640
 04641            if (doOclTonemap && isHwDecoder)
 4642            {
 4643                // map from qsv to opencl via qsv(d3d11)-opencl interop.
 04644                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4645            }
 4646
 4647            // hw tonemap
 04648            if (doOclTonemap)
 4649            {
 04650                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04651                mainFilters.Add(tonemapFilter);
 4652            }
 4653
 04654            var memoryOutput = false;
 04655            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04656            var isHwmapUsable = isSwEncoder && doOclTonemap;
 04657            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4658            {
 04659                memoryOutput = true;
 4660
 4661                // OUTPUT nv12 surface(memory)
 4662                // prefer hwmap to hwdownload on opencl.
 4663                // qsv hwmap is not fully implemented for the time being.
 04664                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04665                mainFilters.Add("format=nv12");
 4666            }
 4667
 4668            // OUTPUT nv12 surface(memory)
 04669            if (isSwDecoder && isQsvEncoder)
 4670            {
 04671                memoryOutput = true;
 4672            }
 4673
 04674            if (memoryOutput)
 4675            {
 4676                // text subtitles
 04677                if (hasTextSubs)
 4678                {
 04679                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04680                    mainFilters.Add(textSubtitlesFilter);
 4681                }
 4682            }
 4683
 04684            if (isQsvInQsvOut && doOclTonemap)
 4685            {
 4686                // OUTPUT qsv(nv12) surface(vram)
 4687                // reverse-mapping via qsv(d3d11)-opencl interop.
 04688                mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1");
 04689                mainFilters.Add("format=qsv");
 4690            }
 4691
 4692            /* Make sub and overlay filters for subtitle stream */
 04693            var subFilters = new List<string>();
 04694            var overlayFilters = new List<string>();
 04695            if (isQsvInQsvOut)
 4696            {
 04697                if (hasSubs)
 4698                {
 04699                    if (hasGraphicalSubs)
 4700                    {
 4701                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04702                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04703                        subFilters.Add(subPreProcFilters);
 04704                        subFilters.Add("format=bgra");
 4705                    }
 04706                    else if (hasTextSubs)
 4707                    {
 04708                        var framerate = state.VideoStream?.RealFrameRate;
 04709                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4710
 4711                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 04712                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04713                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04714                        subFilters.Add(alphaSrcFilter);
 04715                        subFilters.Add("format=bgra");
 04716                        subFilters.Add(subTextSubtitlesFilter);
 4717                    }
 4718
 4719                    // qsv requires a fixed pool size.
 4720                    // default to 64 otherwise it will fail on certain iGPU.
 04721                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4722
 04723                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04724                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04725                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04726                        : string.Empty;
 04727                    var overlayQsvFilter = string.Format(
 04728                        CultureInfo.InvariantCulture,
 04729                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04730                        overlaySize);
 04731                    overlayFilters.Add(overlayQsvFilter);
 4732                }
 4733            }
 04734            else if (memoryOutput)
 4735            {
 04736                if (hasGraphicalSubs)
 4737                {
 04738                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04739                    subFilters.Add(subPreProcFilters);
 04740                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4741                }
 4742            }
 4743
 04744            return (mainFilters, subFilters, overlayFilters);
 4745        }
 4746
 4747        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvVaapiVidFilte
 4748            EncodingJobInfo state,
 4749            EncodingOptions options,
 4750            string vidDecoder,
 4751            string vidEncoder)
 4752        {
 04753            var inW = state.VideoStream?.Width;
 04754            var inH = state.VideoStream?.Height;
 04755            var reqW = state.BaseRequest.Width;
 04756            var reqH = state.BaseRequest.Height;
 04757            var reqMaxW = state.BaseRequest.MaxWidth;
 04758            var reqMaxH = state.BaseRequest.MaxHeight;
 04759            var threeDFormat = state.MediaSource.Video3DFormat;
 4760
 04761            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04762            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04763            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04764            var isHwDecoder = isVaapiDecoder || isQsvDecoder;
 04765            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04766            var isSwEncoder = !isQsvEncoder;
 04767            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04768            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4769
 04770            var doVaVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04771            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04772            var doTonemap = doVaVppTonemap || doOclTonemap;
 04773            var doDeintH2645 = IsDeinterlaceAvailable(state);
 4774
 04775            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04776            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04777            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04778            var hasAssSubs = hasSubs
 04779                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04780                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04781            var subW = state.SubtitleStream?.Width;
 04782            var subH = state.SubtitleStream?.Height;
 4783
 04784            var rotation = state.VideoStream?.Rotation ?? 0;
 04785            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04786            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04787            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isVaapiDecoder || isQsvDecoder) && doVppTransp
 04788            var swpInW = swapWAndH ? inH : inW;
 04789            var swpInH = swapWAndH ? inW : inH;
 4790
 4791            /* Make main filters for video stream */
 04792            var mainFilters = new List<string>();
 4793
 04794            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4795
 04796            if (isSwDecoder)
 4797            {
 4798                // INPUT sw surface(memory)
 4799                // sw deint
 04800                if (doDeintH2645)
 4801                {
 04802                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04803                    mainFilters.Add(swDeintFilter);
 4804                }
 4805
 04806                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04807                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04808                if (isMjpegEncoder && !doOclTonemap)
 4809                {
 4810                    // sw decoder + hw mjpeg encoder
 04811                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4812                }
 4813
 4814                // sw scale
 04815                mainFilters.Add(swScaleFilter);
 04816                mainFilters.Add($"format={outFormat}");
 4817
 4818                // keep video at memory except ocl tonemap,
 4819                // since the overhead caused by hwupload >>> using sw filter.
 4820                // sw => hw
 04821                if (doOclTonemap)
 4822                {
 04823                    mainFilters.Add("hwupload=derive_device=opencl");
 4824                }
 4825            }
 04826            else if (isVaapiDecoder || isQsvDecoder)
 4827            {
 04828                var hwFilterSuffix = isVaapiDecoder ? "vaapi" : "qsv";
 04829                var isRext = IsVideoStreamHevcRext(state);
 04830                var doVppFullRangeOut = isMjpegEncoder
 04831                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04832                var doVppScaleModeHq = isMjpegEncoder
 04833                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 4834
 4835                // INPUT vaapi/qsv surface(vram)
 4836                // hw deint
 04837                if (doDeintH2645)
 4838                {
 04839                    var deintFilter = GetHwDeinterlaceFilter(state, options, hwFilterSuffix);
 04840                    mainFilters.Add(deintFilter);
 4841                }
 4842
 4843                // hw transpose(vaapi vpp)
 04844                if (isVaapiDecoder && doVppTranspose)
 4845                {
 04846                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 4847                }
 4848
 04849                var outFormat = doTonemap ? (((isQsvDecoder && doVppTranspose) || isRext) ? "p010" : string.Empty) : "nv
 04850                var swapOutputWandH = isQsvDecoder && doVppTranspose && swapWAndH;
 04851                var hwScalePrefix = isQsvDecoder ? "vpp" : "scale";
 04852                var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, hwFilterSuffix, outFormat, swapOutputWandH, swpInW, 
 4853
 04854                if (!string.IsNullOrEmpty(hwScaleFilter) && isQsvDecoder && doVppTranspose)
 4855                {
 04856                    hwScaleFilter += $":transpose={transposeDir}";
 4857                }
 4858
 04859                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4860                {
 04861                    hwScaleFilter += ((isQsvDecoder && !doVppFullRangeOut) || doOclTonemap) ? string.Empty : ":out_range
 04862                    hwScaleFilter += isQsvDecoder ? (doVppScaleModeHq ? ":scale_mode=hq" : string.Empty) : ":mode=hq";
 4863                }
 4864
 4865                // allocate extra pool sizes for vaapi vpp scale
 04866                if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder)
 4867                {
 04868                    hwScaleFilter += ":extra_hw_frames=24";
 4869                }
 4870
 4871                // hw transpose(qsv vpp) & scale
 04872                mainFilters.Add(hwScaleFilter);
 4873            }
 4874
 4875            // vaapi vpp tonemap
 04876            if (doVaVppTonemap && isHwDecoder)
 4877            {
 04878                if (isQsvDecoder)
 4879                {
 4880                    // map from qsv to vaapi.
 04881                    mainFilters.Add("hwmap=derive_device=vaapi");
 04882                    mainFilters.Add("format=vaapi");
 4883                }
 4884
 04885                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 04886                mainFilters.Add(tonemapFilter);
 4887
 04888                if (isQsvDecoder)
 4889                {
 4890                    // map from vaapi to qsv.
 04891                    mainFilters.Add("hwmap=derive_device=qsv");
 04892                    mainFilters.Add("format=qsv");
 4893                }
 4894            }
 4895
 04896            if (doOclTonemap && isHwDecoder)
 4897            {
 4898                // map from qsv to opencl via qsv(vaapi)-opencl interop.
 04899                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4900            }
 4901
 4902            // ocl tonemap
 04903            if (doOclTonemap)
 4904            {
 04905                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04906                mainFilters.Add(tonemapFilter);
 4907            }
 4908
 04909            var memoryOutput = false;
 04910            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04911            var isHwmapUsable = isSwEncoder && (doOclTonemap || isVaapiDecoder);
 04912            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4913            {
 04914                memoryOutput = true;
 4915
 4916                // OUTPUT nv12 surface(memory)
 4917                // prefer hwmap to hwdownload on opencl/vaapi.
 4918                // qsv hwmap is not fully implemented for the time being.
 04919                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04920                mainFilters.Add("format=nv12");
 4921            }
 4922
 4923            // OUTPUT nv12 surface(memory)
 04924            if (isSwDecoder && isQsvEncoder)
 4925            {
 04926                memoryOutput = true;
 4927            }
 4928
 04929            if (memoryOutput)
 4930            {
 4931                // text subtitles
 04932                if (hasTextSubs)
 4933                {
 04934                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04935                    mainFilters.Add(textSubtitlesFilter);
 4936                }
 4937            }
 4938
 04939            if (isQsvInQsvOut)
 4940            {
 04941                if (doOclTonemap)
 4942                {
 4943                    // OUTPUT qsv(nv12) surface(vram)
 4944                    // reverse-mapping via qsv(vaapi)-opencl interop.
 4945                    // add extra pool size to avoid the 'cannot allocate memory' error on hevc_qsv.
 04946                    mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1:extra_hw_frames=16");
 04947                    mainFilters.Add("format=qsv");
 4948                }
 04949                else if (isVaapiDecoder)
 4950                {
 04951                    mainFilters.Add("hwmap=derive_device=qsv");
 04952                    mainFilters.Add("format=qsv");
 4953                }
 4954            }
 4955
 4956            /* Make sub and overlay filters for subtitle stream */
 04957            var subFilters = new List<string>();
 04958            var overlayFilters = new List<string>();
 04959            if (isQsvInQsvOut)
 4960            {
 04961                if (hasSubs)
 4962                {
 04963                    if (hasGraphicalSubs)
 4964                    {
 4965                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04966                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04967                        subFilters.Add(subPreProcFilters);
 04968                        subFilters.Add("format=bgra");
 4969                    }
 04970                    else if (hasTextSubs)
 4971                    {
 04972                        var framerate = state.VideoStream?.RealFrameRate;
 04973                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4974
 04975                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04976                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04977                        subFilters.Add(alphaSrcFilter);
 04978                        subFilters.Add("format=bgra");
 04979                        subFilters.Add(subTextSubtitlesFilter);
 4980                    }
 4981
 4982                    // qsv requires a fixed pool size.
 4983                    // default to 64 otherwise it will fail on certain iGPU.
 04984                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4985
 04986                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04987                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04988                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04989                        : string.Empty;
 04990                    var overlayQsvFilter = string.Format(
 04991                        CultureInfo.InvariantCulture,
 04992                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04993                        overlaySize);
 04994                    overlayFilters.Add(overlayQsvFilter);
 4995                }
 4996            }
 04997            else if (memoryOutput)
 4998            {
 04999                if (hasGraphicalSubs)
 5000                {
 05001                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05002                    subFilters.Add(subPreProcFilters);
 05003                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5004                }
 5005            }
 5006
 05007            return (mainFilters, subFilters, overlayFilters);
 5008        }
 5009
 5010        /// <summary>
 5011        /// Gets the parameter of Intel/AMD VAAPI filter chain.
 5012        /// </summary>
 5013        /// <param name="state">Encoding state.</param>
 5014        /// <param name="options">Encoding options.</param>
 5015        /// <param name="vidEncoder">Video encoder to use.</param>
 5016        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5017        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiVidFilterChain(
 5018            EncodingJobInfo state,
 5019            EncodingOptions options,
 5020            string vidEncoder)
 5021        {
 05022            if (options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 5023            {
 05024                return (null, null, null);
 5025            }
 5026
 05027            var isLinux = OperatingSystem.IsLinux();
 05028            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05029            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05030            var isSwEncoder = !vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05031            var isVaapiFullSupported = isLinux && IsVaapiSupported(state) && IsVaapiFullSupported();
 05032            var isVaapiOclSupported = isVaapiFullSupported && IsOpenclFullSupported();
 05033            var isVaapiVkSupported = isVaapiFullSupported && IsVulkanFullSupported();
 5034
 5035            // legacy vaapi pipeline(copy-back)
 05036            if ((isSwDecoder && isSwEncoder)
 05037                || !isVaapiOclSupported
 05038                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5039            {
 05040                var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
 5041
 05042                if (!isSwEncoder)
 5043                {
 05044                    var newfilters = new List<string>();
 05045                    var noOverlay = swFilterChain.OverlayFilters.Count == 0;
 05046                    newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
 05047                    newfilters.Add("hwupload=derive_device=vaapi");
 5048
 05049                    var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
 05050                    var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
 05051                    return (mainFilters, swFilterChain.SubFilters, overlayFilters);
 5052                }
 5053
 05054                return swFilterChain;
 5055            }
 5056
 5057            // preferred vaapi + opencl filters pipeline
 05058            if (_mediaEncoder.IsVaapiDeviceInteliHD)
 5059            {
 5060                // Intel iHD path, with extra vpp tonemap and overlay support.
 05061                return GetIntelVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5062            }
 5063
 5064            // preferred vaapi + vulkan filters pipeline
 05065            if (_mediaEncoder.IsVaapiDeviceAmd
 05066                && isVaapiVkSupported
 05067                && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 05068                && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 5069            {
 5070                // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support.
 05071                return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5072            }
 5073
 5074            // Intel i965 and Amd legacy driver path, only featuring scale and deinterlace support.
 05075            return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5076        }
 5077
 5078        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVaapiFullVidFilt
 5079            EncodingJobInfo state,
 5080            EncodingOptions options,
 5081            string vidDecoder,
 5082            string vidEncoder)
 5083        {
 05084            var inW = state.VideoStream?.Width;
 05085            var inH = state.VideoStream?.Height;
 05086            var reqW = state.BaseRequest.Width;
 05087            var reqH = state.BaseRequest.Height;
 05088            var reqMaxW = state.BaseRequest.MaxWidth;
 05089            var reqMaxH = state.BaseRequest.MaxHeight;
 05090            var threeDFormat = state.MediaSource.Video3DFormat;
 5091
 05092            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05093            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05094            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05095            var isSwEncoder = !isVaapiEncoder;
 05096            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05097            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 5098
 05099            var doVaVppTonemap = isVaapiDecoder && IsIntelVppTonemapAvailable(state, options);
 05100            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 05101            var doTonemap = doVaVppTonemap || doOclTonemap;
 05102            var doDeintH2645 = IsDeinterlaceAvailable(state);
 5103
 05104            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05105            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05106            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05107            var hasAssSubs = hasSubs
 05108                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05109                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 05110            var subW = state.SubtitleStream?.Width;
 05111            var subH = state.SubtitleStream?.Height;
 5112
 05113            var rotation = state.VideoStream?.Rotation ?? 0;
 05114            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05115            var doVaVppTranspose = !string.IsNullOrEmpty(transposeDir);
 05116            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVaVppTranspose));
 05117            var swpInW = swapWAndH ? inH : inW;
 05118            var swpInH = swapWAndH ? inW : inH;
 5119
 5120            /* Make main filters for video stream */
 05121            var mainFilters = new List<string>();
 5122
 05123            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 5124
 05125            if (isSwDecoder)
 5126            {
 5127                // INPUT sw surface(memory)
 5128                // sw deint
 05129                if (doDeintH2645)
 5130                {
 05131                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05132                    mainFilters.Add(swDeintFilter);
 5133                }
 5134
 05135                var outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05136                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05137                if (isMjpegEncoder && !doOclTonemap)
 5138                {
 5139                    // sw decoder + hw mjpeg encoder
 05140                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5141                }
 5142
 5143                // sw scale
 05144                mainFilters.Add(swScaleFilter);
 05145                mainFilters.Add($"format={outFormat}");
 5146
 5147                // keep video at memory except ocl tonemap,
 5148                // since the overhead caused by hwupload >>> using sw filter.
 5149                // sw => hw
 05150                if (doOclTonemap)
 5151                {
 05152                    mainFilters.Add("hwupload=derive_device=opencl");
 5153                }
 5154            }
 05155            else if (isVaapiDecoder)
 5156            {
 05157                var isRext = IsVideoStreamHevcRext(state);
 5158
 5159                // INPUT vaapi surface(vram)
 5160                // hw deint
 05161                if (doDeintH2645)
 5162                {
 05163                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05164                    mainFilters.Add(deintFilter);
 5165                }
 5166
 5167                // hw transpose
 05168                if (doVaVppTranspose)
 5169                {
 05170                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 5171                }
 5172
 05173                var outFormat = doTonemap ? (isRext ? "p010" : string.Empty) : "nv12";
 05174                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, swpInW, swpInH, reqW, reqH, req
 5175
 05176                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5177                {
 05178                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05179                    hwScaleFilter += ":mode=hq";
 5180                }
 5181
 5182                // allocate extra pool sizes for vaapi vpp
 05183                if (!string.IsNullOrEmpty(hwScaleFilter))
 5184                {
 05185                    hwScaleFilter += ":extra_hw_frames=24";
 5186                }
 5187
 5188                // hw scale
 05189                mainFilters.Add(hwScaleFilter);
 5190            }
 5191
 5192            // vaapi vpp tonemap
 05193            if (doVaVppTonemap && isVaapiDecoder)
 5194            {
 05195                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 05196                mainFilters.Add(tonemapFilter);
 5197            }
 5198
 05199            if (doOclTonemap && isVaapiDecoder)
 5200            {
 5201                // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05202                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 5203            }
 5204
 5205            // ocl tonemap
 05206            if (doOclTonemap)
 5207            {
 05208                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05209                mainFilters.Add(tonemapFilter);
 5210            }
 5211
 05212            if (doOclTonemap && isVaInVaOut)
 5213            {
 5214                // OUTPUT vaapi(nv12) surface(vram)
 5215                // reverse-mapping via vaapi-opencl interop.
 05216                mainFilters.Add("hwmap=derive_device=vaapi:mode=write:reverse=1");
 05217                mainFilters.Add("format=vaapi");
 5218            }
 5219
 05220            var memoryOutput = false;
 05221            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 05222            var isHwmapNotUsable = isUploadForOclTonemap && isVaapiEncoder;
 05223            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap)
 5224            {
 05225                memoryOutput = true;
 5226
 5227                // OUTPUT nv12 surface(memory)
 5228                // prefer hwmap to hwdownload on opencl/vaapi.
 05229                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap=mode=read");
 05230                mainFilters.Add("format=nv12");
 5231            }
 5232
 5233            // OUTPUT nv12 surface(memory)
 05234            if (isSwDecoder && isVaapiEncoder)
 5235            {
 05236                memoryOutput = true;
 5237            }
 5238
 05239            if (memoryOutput)
 5240            {
 5241                // text subtitles
 05242                if (hasTextSubs)
 5243                {
 05244                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05245                    mainFilters.Add(textSubtitlesFilter);
 5246                }
 5247            }
 5248
 05249            if (memoryOutput && isVaapiEncoder)
 5250            {
 05251                if (!hasGraphicalSubs)
 5252                {
 05253                    mainFilters.Add("hwupload_vaapi");
 5254                }
 5255            }
 5256
 5257            /* Make sub and overlay filters for subtitle stream */
 05258            var subFilters = new List<string>();
 05259            var overlayFilters = new List<string>();
 05260            if (isVaInVaOut)
 5261            {
 05262                if (hasSubs)
 5263                {
 05264                    if (hasGraphicalSubs)
 5265                    {
 5266                        // overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead
 05267                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 05268                        subFilters.Add(subPreProcFilters);
 05269                        subFilters.Add("format=bgra");
 5270                    }
 05271                    else if (hasTextSubs)
 5272                    {
 05273                        var framerate = state.VideoStream?.RealFrameRate;
 05274                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5275
 05276                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 05277                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05278                        subFilters.Add(alphaSrcFilter);
 05279                        subFilters.Add("format=bgra");
 05280                        subFilters.Add(subTextSubtitlesFilter);
 5281                    }
 5282
 05283                    subFilters.Add("hwupload=derive_device=vaapi");
 5284
 05285                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 05286                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 05287                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 05288                        : string.Empty;
 05289                    var overlayVaapiFilter = string.Format(
 05290                        CultureInfo.InvariantCulture,
 05291                        "overlay_vaapi=eof_action=pass:repeatlast=0{0}",
 05292                        overlaySize);
 05293                    overlayFilters.Add(overlayVaapiFilter);
 5294                }
 5295            }
 05296            else if (memoryOutput)
 5297            {
 05298                if (hasGraphicalSubs)
 5299                {
 05300                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05301                    subFilters.Add(subPreProcFilters);
 05302                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5303
 05304                    if (isVaapiEncoder)
 5305                    {
 05306                        overlayFilters.Add("hwupload_vaapi");
 5307                    }
 5308                }
 5309            }
 5310
 05311            return (mainFilters, subFilters, overlayFilters);
 5312        }
 5313
 5314        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVaapiFullVidFilter
 5315            EncodingJobInfo state,
 5316            EncodingOptions options,
 5317            string vidDecoder,
 5318            string vidEncoder)
 5319        {
 05320            var inW = state.VideoStream?.Width;
 05321            var inH = state.VideoStream?.Height;
 05322            var reqW = state.BaseRequest.Width;
 05323            var reqH = state.BaseRequest.Height;
 05324            var reqMaxW = state.BaseRequest.MaxWidth;
 05325            var reqMaxH = state.BaseRequest.MaxHeight;
 05326            var threeDFormat = state.MediaSource.Video3DFormat;
 5327
 05328            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05329            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05330            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05331            var isSwEncoder = !isVaapiEncoder;
 05332            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5333
 05334            var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
 05335            var doDeintH2645 = IsDeinterlaceAvailable(state);
 5336
 05337            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05338            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05339            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05340            var hasAssSubs = hasSubs
 05341                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05342                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5343
 05344            var rotation = state.VideoStream?.Rotation ?? 0;
 05345            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05346            var doVkTranspose = isVaapiDecoder && !string.IsNullOrEmpty(transposeDir);
 05347            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVkTranspose));
 05348            var swpInW = swapWAndH ? inH : inW;
 05349            var swpInH = swapWAndH ? inW : inH;
 5350
 5351            /* Make main filters for video stream */
 05352            var mainFilters = new List<string>();
 5353
 05354            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doVkTonemap));
 5355
 05356            if (isSwDecoder)
 5357            {
 5358                // INPUT sw surface(memory)
 5359                // sw deint
 05360                if (doDeintH2645)
 5361                {
 05362                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05363                    mainFilters.Add(swDeintFilter);
 5364                }
 5365
 05366                if (doVkTonemap || hasSubs)
 5367                {
 5368                    // sw => hw
 05369                    mainFilters.Add("hwupload=derive_device=vulkan");
 05370                    mainFilters.Add("format=vulkan");
 5371                }
 5372                else
 5373                {
 5374                    // sw scale
 05375                    var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW,
 05376                    mainFilters.Add(swScaleFilter);
 05377                    mainFilters.Add("format=nv12");
 5378                }
 5379            }
 05380            else if (isVaapiDecoder)
 5381            {
 5382                // INPUT vaapi surface(vram)
 05383                if (doVkTranspose || doVkTonemap || hasSubs)
 5384                {
 5385                    // map from vaapi to vulkan/drm via interop (Polaris/gfx8+).
 05386                    if (_mediaEncoder.EncoderVersion >= _minFFmpegAlteredVaVkInterop)
 5387                    {
 05388                        if (doVkTranspose || !_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5389                        {
 5390                            // disable the indirect va-drm-vk mapping since it's no longer reliable.
 05391                            mainFilters.Add("hwmap=derive_device=drm");
 05392                            mainFilters.Add("format=drm_prime");
 05393                            mainFilters.Add("hwmap=derive_device=vulkan");
 05394                            mainFilters.Add("format=vulkan");
 5395
 5396                            // workaround for libplacebo using the imported vulkan frame on gfx8.
 05397                            if (!_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5398                            {
 05399                                mainFilters.Add("scale_vulkan");
 5400                            }
 5401                        }
 05402                        else if (doVkTonemap || hasSubs)
 5403                        {
 5404                            // non ad-hoc libplacebo also accepts drm_prime direct input.
 05405                            mainFilters.Add("hwmap=derive_device=drm");
 05406                            mainFilters.Add("format=drm_prime");
 5407                        }
 5408                    }
 5409                    else // legacy va-vk mapping that works only in jellyfin-ffmpeg6
 5410                    {
 05411                        mainFilters.Add("hwmap=derive_device=vulkan");
 05412                        mainFilters.Add("format=vulkan");
 5413                    }
 5414                }
 5415                else
 5416                {
 5417                    // hw deint
 05418                    if (doDeintH2645)
 5419                    {
 05420                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05421                        mainFilters.Add(deintFilter);
 5422                    }
 5423
 5424                    // hw scale
 05425                    var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", "nv12", false, inW, inH, reqW, reqH, reqMaxW,
 5426
 05427                    if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder && !doVkTonemap)
 5428                    {
 05429                        hwScaleFilter += ":out_range=pc:mode=hq";
 5430                    }
 5431
 05432                    mainFilters.Add(hwScaleFilter);
 5433                }
 5434            }
 5435
 5436            // vk transpose
 05437            if (doVkTranspose)
 5438            {
 05439                if (string.Equals(transposeDir, "reversal", StringComparison.OrdinalIgnoreCase))
 5440                {
 05441                    mainFilters.Add("flip_vulkan");
 5442                }
 5443                else
 5444                {
 05445                    mainFilters.Add($"transpose_vulkan=dir={transposeDir}");
 5446                }
 5447            }
 5448
 5449            // vk libplacebo
 05450            if (doVkTonemap || hasSubs)
 5451            {
 05452                var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, req
 05453                mainFilters.Add(libplaceboFilter);
 05454                mainFilters.Add("format=vulkan");
 5455            }
 5456
 05457            if (doVkTonemap && !hasSubs)
 5458            {
 5459                // OUTPUT vaapi(nv12) surface(vram)
 5460                // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05461                mainFilters.Add("hwmap=derive_device=vaapi");
 05462                mainFilters.Add("format=vaapi");
 5463
 5464                // clear the surf->meta_offset and output nv12
 05465                mainFilters.Add("scale_vaapi=format=nv12");
 5466
 5467                // hw deint
 05468                if (doDeintH2645)
 5469                {
 05470                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05471                    mainFilters.Add(deintFilter);
 5472                }
 5473            }
 5474
 05475            if (!hasSubs)
 5476            {
 5477                // OUTPUT nv12 surface(memory)
 05478                if (isSwEncoder && (doVkTonemap || isVaapiDecoder))
 5479                {
 05480                    mainFilters.Add("hwdownload");
 05481                    mainFilters.Add("format=nv12");
 5482                }
 5483
 05484                if (isSwDecoder && isVaapiEncoder && !doVkTonemap)
 5485                {
 05486                    mainFilters.Add("hwupload_vaapi");
 5487                }
 5488            }
 5489
 5490            /* Make sub and overlay filters for subtitle stream */
 05491            var subFilters = new List<string>();
 05492            var overlayFilters = new List<string>();
 05493            if (hasSubs)
 5494            {
 05495                if (hasGraphicalSubs)
 5496                {
 05497                    var subW = state.SubtitleStream?.Width;
 05498                    var subH = state.SubtitleStream?.Height;
 05499                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05500                    subFilters.Add(subPreProcFilters);
 05501                    subFilters.Add("format=bgra");
 5502                }
 05503                else if (hasTextSubs)
 5504                {
 05505                    var framerate = state.VideoStream?.RealFrameRate;
 05506                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5507
 05508                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05509                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05510                    subFilters.Add(alphaSrcFilter);
 05511                    subFilters.Add("format=bgra");
 05512                    subFilters.Add(subTextSubtitlesFilter);
 5513                }
 5514
 05515                subFilters.Add("hwupload=derive_device=vulkan");
 05516                subFilters.Add("format=vulkan");
 5517
 05518                overlayFilters.Add("overlay_vulkan=eof_action=pass:repeatlast=0");
 5519
 05520                if (isSwEncoder)
 5521                {
 5522                    // OUTPUT nv12 surface(memory)
 05523                    overlayFilters.Add("scale_vulkan=format=nv12");
 05524                    overlayFilters.Add("hwdownload");
 05525                    overlayFilters.Add("format=nv12");
 5526                }
 05527                else if (isVaapiEncoder)
 5528                {
 5529                    // OUTPUT vaapi(nv12) surface(vram)
 5530                    // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05531                    overlayFilters.Add("hwmap=derive_device=vaapi");
 05532                    overlayFilters.Add("format=vaapi");
 5533
 5534                    // clear the surf->meta_offset and output nv12
 05535                    overlayFilters.Add("scale_vaapi=format=nv12");
 5536
 5537                    // hw deint
 05538                    if (doDeintH2645)
 5539                    {
 05540                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05541                        overlayFilters.Add(deintFilter);
 5542                    }
 5543                }
 5544            }
 5545
 05546            return (mainFilters, subFilters, overlayFilters);
 5547        }
 5548
 5549        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiLimitedVidFilter
 5550            EncodingJobInfo state,
 5551            EncodingOptions options,
 5552            string vidDecoder,
 5553            string vidEncoder)
 5554        {
 05555            var inW = state.VideoStream?.Width;
 05556            var inH = state.VideoStream?.Height;
 05557            var reqW = state.BaseRequest.Width;
 05558            var reqH = state.BaseRequest.Height;
 05559            var reqMaxW = state.BaseRequest.MaxWidth;
 05560            var reqMaxH = state.BaseRequest.MaxHeight;
 05561            var threeDFormat = state.MediaSource.Video3DFormat;
 5562
 05563            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05564            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05565            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05566            var isSwEncoder = !isVaapiEncoder;
 05567            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05568            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 05569            var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965;
 05570            var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd;
 5571
 05572            var doDeintH2645 = IsDeinterlaceAvailable(state);
 05573            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5574
 05575            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05576            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05577            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 5578
 05579            var rotation = state.VideoStream?.Rotation ?? 0;
 05580            var swapWAndH = Math.Abs(rotation) == 90 && isSwDecoder;
 05581            var swpInW = swapWAndH ? inH : inW;
 05582            var swpInH = swapWAndH ? inW : inH;
 5583
 5584            /* Make main filters for video stream */
 05585            var mainFilters = new List<string>();
 5586
 05587            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5588
 05589            var outFormat = string.Empty;
 05590            if (isSwDecoder)
 5591            {
 5592                // INPUT sw surface(memory)
 5593                // sw deint
 05594                if (doDeintH2645)
 5595                {
 05596                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05597                    mainFilters.Add(swDeintFilter);
 5598                }
 5599
 05600                outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05601                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05602                if (isMjpegEncoder && !doOclTonemap)
 5603                {
 5604                    // sw decoder + hw mjpeg encoder
 05605                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5606                }
 5607
 5608                // sw scale
 05609                mainFilters.Add(swScaleFilter);
 05610                mainFilters.Add("format=" + outFormat);
 5611
 5612                // keep video at memory except ocl tonemap,
 5613                // since the overhead caused by hwupload >>> using sw filter.
 5614                // sw => hw
 05615                if (doOclTonemap)
 5616                {
 05617                    mainFilters.Add("hwupload=derive_device=opencl");
 5618                }
 5619            }
 05620            else if (isVaapiDecoder)
 5621            {
 5622                // INPUT vaapi surface(vram)
 5623                // hw deint
 05624                if (doDeintH2645)
 5625                {
 05626                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05627                    mainFilters.Add(deintFilter);
 5628                }
 5629
 05630                outFormat = doOclTonemap ? string.Empty : "nv12";
 05631                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, inW, inH, reqW, reqH, reqMaxW, 
 5632
 05633                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5634                {
 05635                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05636                    hwScaleFilter += ":mode=hq";
 5637                }
 5638
 5639                // allocate extra pool sizes for vaapi vpp
 05640                if (!string.IsNullOrEmpty(hwScaleFilter))
 5641                {
 05642                    hwScaleFilter += ":extra_hw_frames=24";
 5643                }
 5644
 5645                // hw scale
 05646                mainFilters.Add(hwScaleFilter);
 5647            }
 5648
 05649            if (doOclTonemap && isVaapiDecoder)
 5650            {
 05651                if (isi965Driver)
 5652                {
 5653                    // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05654                    mainFilters.Add("hwmap=derive_device=opencl");
 5655                }
 5656                else
 5657                {
 05658                    mainFilters.Add("hwdownload");
 05659                    mainFilters.Add("format=p010le");
 05660                    mainFilters.Add("hwupload=derive_device=opencl");
 5661                }
 5662            }
 5663
 5664            // ocl tonemap
 05665            if (doOclTonemap)
 5666            {
 05667                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05668                mainFilters.Add(tonemapFilter);
 5669            }
 5670
 05671            if (doOclTonemap && isVaInVaOut)
 5672            {
 05673                if (isi965Driver)
 5674                {
 5675                    // OUTPUT vaapi(nv12) surface(vram)
 5676                    // reverse-mapping via vaapi-opencl interop.
 05677                    mainFilters.Add("hwmap=derive_device=vaapi:reverse=1");
 05678                    mainFilters.Add("format=vaapi");
 5679                }
 5680            }
 5681
 05682            var memoryOutput = false;
 05683            var isUploadForOclTonemap = doOclTonemap && (isSwDecoder || (isVaapiDecoder && !isi965Driver));
 05684            var isHwmapNotUsable = hasGraphicalSubs || isUploadForOclTonemap;
 05685            var isHwmapForSubs = hasSubs && isVaapiDecoder;
 05686            var isHwUnmapForTextSubs = hasTextSubs && isVaInVaOut && !isUploadForOclTonemap;
 05687            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap || isHwmapForSubs)
 5688            {
 05689                memoryOutput = true;
 5690
 5691                // OUTPUT nv12 surface(memory)
 5692                // prefer hwmap to hwdownload on opencl/vaapi.
 05693                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap");
 05694                mainFilters.Add("format=nv12");
 5695            }
 5696
 5697            // OUTPUT nv12 surface(memory)
 05698            if (isSwDecoder && isVaapiEncoder)
 5699            {
 05700                memoryOutput = true;
 5701            }
 5702
 05703            if (memoryOutput)
 5704            {
 5705                // text subtitles
 05706                if (hasTextSubs)
 5707                {
 05708                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05709                    mainFilters.Add(textSubtitlesFilter);
 5710                }
 5711            }
 5712
 05713            if (isHwUnmapForTextSubs)
 5714            {
 05715                mainFilters.Add("hwmap");
 05716                mainFilters.Add("format=vaapi");
 5717            }
 05718            else if (memoryOutput && isVaapiEncoder)
 5719            {
 05720                if (!hasGraphicalSubs)
 5721                {
 05722                    mainFilters.Add("hwupload_vaapi");
 5723                }
 5724            }
 5725
 5726            /* Make sub and overlay filters for subtitle stream */
 05727            var subFilters = new List<string>();
 05728            var overlayFilters = new List<string>();
 05729            if (memoryOutput)
 5730            {
 05731                if (hasGraphicalSubs)
 5732                {
 05733                    var subW = state.SubtitleStream?.Width;
 05734                    var subH = state.SubtitleStream?.Height;
 05735                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05736                    subFilters.Add(subPreProcFilters);
 05737                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5738
 05739                    if (isVaapiEncoder)
 5740                    {
 05741                        overlayFilters.Add("hwupload_vaapi");
 5742                    }
 5743                }
 5744            }
 5745
 05746            return (mainFilters, subFilters, overlayFilters);
 5747        }
 5748
 5749        /// <summary>
 5750        /// Gets the parameter of Apple VideoToolBox filter chain.
 5751        /// </summary>
 5752        /// <param name="state">Encoding state.</param>
 5753        /// <param name="options">Encoding options.</param>
 5754        /// <param name="vidEncoder">Video encoder to use.</param>
 5755        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5756        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFilterChain(
 5757            EncodingJobInfo state,
 5758            EncodingOptions options,
 5759            string vidEncoder)
 5760        {
 05761            if (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 5762            {
 05763                return (null, null, null);
 5764            }
 5765
 5766            // ReSharper disable once InconsistentNaming
 05767            var isMacOS = OperatingSystem.IsMacOS();
 05768            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05769            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05770            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05771            var isVtFullSupported = isMacOS && IsVideoToolboxFullSupported();
 5772
 5773            // legacy videotoolbox pipeline (disable hw filters)
 05774            if (!(isVtEncoder || isVtDecoder)
 05775                || !isVtFullSupported
 05776                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5777            {
 05778                return GetSwVidFilterChain(state, options, vidEncoder);
 5779            }
 5780
 5781            // preferred videotoolbox + metal filters pipeline
 05782            return GetAppleVidFiltersPreferred(state, options, vidDecoder, vidEncoder);
 5783        }
 5784
 5785        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFiltersPrefer
 5786            EncodingJobInfo state,
 5787            EncodingOptions options,
 5788            string vidDecoder,
 5789            string vidEncoder)
 5790        {
 05791            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05792            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05793            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5794
 05795            var inW = state.VideoStream?.Width;
 05796            var inH = state.VideoStream?.Height;
 05797            var reqW = state.BaseRequest.Width;
 05798            var reqH = state.BaseRequest.Height;
 05799            var reqMaxW = state.BaseRequest.MaxWidth;
 05800            var reqMaxH = state.BaseRequest.MaxHeight;
 05801            var threeDFormat = state.MediaSource.Video3DFormat;
 5802
 05803            var doDeintH2645 = IsDeinterlaceAvailable(state);
 05804            var doVtTonemap = IsVideoToolboxTonemapAvailable(state, options);
 05805            var doMetalTonemap = !doVtTonemap && IsHwTonemapAvailable(state, options);
 05806            var usingHwSurface = isVtDecoder && (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface);
 5807
 05808            var rotation = state.VideoStream?.Rotation ?? 0;
 05809            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05810            var doVtTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_vt");
 05811            var swapWAndH = Math.Abs(rotation) == 90 && doVtTranspose;
 05812            var swpInW = swapWAndH ? inH : inW;
 05813            var swpInH = swapWAndH ? inW : inH;
 5814
 05815            var scaleFormat = string.Empty;
 5816            // Use P010 for Metal tone mapping, otherwise force an 8bit output.
 05817            if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase))
 5818            {
 05819                if (doMetalTonemap)
 5820                {
 05821                    if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 5822                    {
 05823                        scaleFormat = "p010le";
 5824                    }
 5825                }
 5826                else
 5827                {
 05828                    scaleFormat = "nv12";
 5829                }
 5830            }
 5831
 05832            var hwScaleFilter = GetHwScaleFilter("scale", "vt", scaleFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW,
 5833
 05834            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05835            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05836            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05837            var hasAssSubs = hasSubs
 05838                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05839                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5840
 5841            /* Make main filters for video stream */
 05842            var mainFilters = new List<string>();
 5843
 5844            // hw deint
 05845            if (doDeintH2645)
 5846            {
 05847                var deintFilter = GetHwDeinterlaceFilter(state, options, "videotoolbox");
 05848                mainFilters.Add(deintFilter);
 5849            }
 5850
 5851            // hw transpose
 05852            if (doVtTranspose)
 5853            {
 05854                mainFilters.Add($"transpose_vt=dir={transposeDir}");
 5855            }
 5856
 05857            if (doVtTonemap)
 5858            {
 5859                const string VtTonemapArgs = "color_matrix=bt709:color_primaries=bt709:color_transfer=bt709";
 5860
 5861                // scale_vt can handle scaling & tonemapping in one shot, just like vpp_qsv.
 05862                hwScaleFilter = string.IsNullOrEmpty(hwScaleFilter)
 05863                    ? "scale_vt=" + VtTonemapArgs
 05864                    : hwScaleFilter + ":" + VtTonemapArgs;
 5865            }
 5866
 5867            // hw scale & vt tonemap
 05868            mainFilters.Add(hwScaleFilter);
 5869
 5870            // Metal tonemap
 05871            if (doMetalTonemap)
 5872            {
 05873                var tonemapFilter = GetHwTonemapFilter(options, "videotoolbox", "nv12", isMjpegEncoder);
 05874                mainFilters.Add(tonemapFilter);
 5875            }
 5876
 5877            /* Make sub and overlay filters for subtitle stream */
 05878            var subFilters = new List<string>();
 05879            var overlayFilters = new List<string>();
 5880
 05881            if (hasSubs)
 5882            {
 05883                if (hasGraphicalSubs)
 5884                {
 05885                    var subW = state.SubtitleStream?.Width;
 05886                    var subH = state.SubtitleStream?.Height;
 05887                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05888                    subFilters.Add(subPreProcFilters);
 05889                    subFilters.Add("format=bgra");
 5890                }
 05891                else if (hasTextSubs)
 5892                {
 05893                    var framerate = state.VideoStream?.RealFrameRate;
 05894                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5895
 05896                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05897                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05898                    subFilters.Add(alphaSrcFilter);
 05899                    subFilters.Add("format=bgra");
 05900                    subFilters.Add(subTextSubtitlesFilter);
 5901                }
 5902
 05903                subFilters.Add("hwupload");
 05904                overlayFilters.Add("overlay_videotoolbox=eof_action=pass:repeatlast=0");
 5905            }
 5906
 05907            if (usingHwSurface)
 5908            {
 05909                if (!isVtEncoder)
 5910                {
 05911                    mainFilters.Add("hwdownload");
 05912                    mainFilters.Add("format=nv12");
 5913                }
 5914
 05915                return (mainFilters, subFilters, overlayFilters);
 5916            }
 5917
 5918            // For old jellyfin-ffmpeg that has broken hwsurface, add a hwupload
 05919            var needFiltering = mainFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05920                                subFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05921                                overlayFilters.Any(f => !string.IsNullOrEmpty(f));
 05922            if (needFiltering)
 5923            {
 5924                // INPUT videotoolbox/memory surface(vram/uma)
 5925                // this will pass-through automatically if in/out format matches.
 05926                mainFilters.Insert(0, "hwupload");
 05927                mainFilters.Insert(0, "format=nv12|p010le|videotoolbox_vld");
 5928
 05929                if (!isVtEncoder)
 5930                {
 05931                    mainFilters.Add("hwdownload");
 05932                    mainFilters.Add("format=nv12");
 5933                }
 5934            }
 5935
 05936            return (mainFilters, subFilters, overlayFilters);
 5937        }
 5938
 5939        /// <summary>
 5940        /// Gets the parameter of Rockchip RKMPP/RKRGA filter chain.
 5941        /// </summary>
 5942        /// <param name="state">Encoding state.</param>
 5943        /// <param name="options">Encoding options.</param>
 5944        /// <param name="vidEncoder">Video encoder to use.</param>
 5945        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5946        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFilterChain(
 5947            EncodingJobInfo state,
 5948            EncodingOptions options,
 5949            string vidEncoder)
 5950        {
 05951            if (options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 5952            {
 05953                return (null, null, null);
 5954            }
 5955
 05956            var isLinux = OperatingSystem.IsLinux();
 05957            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05958            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05959            var isSwEncoder = !vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05960            var isRkmppOclSupported = isLinux && IsRkmppFullSupported() && IsOpenclFullSupported();
 5961
 05962            if ((isSwDecoder && isSwEncoder)
 05963                || !isRkmppOclSupported
 05964                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5965            {
 05966                return GetSwVidFilterChain(state, options, vidEncoder);
 5967            }
 5968
 5969            // preferred rkmpp + rkrga + opencl filters pipeline
 05970            if (isRkmppOclSupported)
 5971            {
 05972                return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5973            }
 5974
 05975            return (null, null, null);
 5976        }
 5977
 5978        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFiltersPrefer
 5979            EncodingJobInfo state,
 5980            EncodingOptions options,
 5981            string vidDecoder,
 5982            string vidEncoder)
 5983        {
 05984            var inW = state.VideoStream?.Width;
 05985            var inH = state.VideoStream?.Height;
 05986            var reqW = state.BaseRequest.Width;
 05987            var reqH = state.BaseRequest.Height;
 05988            var reqMaxW = state.BaseRequest.MaxWidth;
 05989            var reqMaxH = state.BaseRequest.MaxHeight;
 05990            var threeDFormat = state.MediaSource.Video3DFormat;
 5991
 05992            var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05993            var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05994            var isSwDecoder = !isRkmppDecoder;
 05995            var isSwEncoder = !isRkmppEncoder;
 05996            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05997            var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder;
 05998            var isEncoderSupportAfbc = isRkmppEncoder
 05999                && (vidEncoder.Contains("h264", StringComparison.OrdinalIgnoreCase)
 06000                    || vidEncoder.Contains("hevc", StringComparison.OrdinalIgnoreCase));
 6001
 06002            var doDeintH2645 = IsDeinterlaceAvailable(state);
 06003            var doOclTonemap = IsHwTonemapAvailable(state, options);
 6004
 06005            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 06006            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 06007            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 06008            var hasAssSubs = hasSubs
 06009                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 06010                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 06011            var subW = state.SubtitleStream?.Width;
 06012            var subH = state.SubtitleStream?.Height;
 6013
 06014            var rotation = state.VideoStream?.Rotation ?? 0;
 06015            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 06016            var doRkVppTranspose = !string.IsNullOrEmpty(transposeDir);
 06017            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isRkmppDecoder && doRkVppTranspose));
 06018            var swpInW = swapWAndH ? inH : inW;
 06019            var swpInH = swapWAndH ? inW : inH;
 6020
 6021            /* Make main filters for video stream */
 06022            var mainFilters = new List<string>();
 6023
 06024            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 6025
 06026            if (isSwDecoder)
 6027            {
 6028                // INPUT sw surface(memory)
 6029                // sw deint
 06030                if (doDeintH2645)
 6031                {
 06032                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 06033                    mainFilters.Add(swDeintFilter);
 6034                }
 6035
 06036                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 06037                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 06038                if (isMjpegEncoder && !doOclTonemap)
 6039                {
 6040                    // sw decoder + hw mjpeg encoder
 06041                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 6042                }
 6043
 06044                if (!string.IsNullOrEmpty(swScaleFilter))
 6045                {
 06046                    swScaleFilter += ":flags=fast_bilinear";
 6047                }
 6048
 6049                // sw scale
 06050                mainFilters.Add(swScaleFilter);
 06051                mainFilters.Add($"format={outFormat}");
 6052
 6053                // keep video at memory except ocl tonemap,
 6054                // since the overhead caused by hwupload >>> using sw filter.
 6055                // sw => hw
 06056                if (doOclTonemap)
 6057                {
 06058                    mainFilters.Add("hwupload=derive_device=opencl");
 6059                }
 6060            }
 06061            else if (isRkmppDecoder)
 6062            {
 6063                // INPUT rkmpp/drm surface(gem/dma-heap)
 6064
 06065                var isFullAfbcPipeline = isEncoderSupportAfbc && isDrmInDrmOut && !doOclTonemap;
 06066                var swapOutputWandH = doRkVppTranspose && swapWAndH;
 06067                var outFormat = doOclTonemap ? "p010" : "nv12";
 06068                var hwScaleFilter = GetHwScaleFilter("vpp", "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, r
 06069                var doScaling = !string.IsNullOrEmpty(GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, sw
 6070
 06071                if (!hasSubs
 06072                     || doRkVppTranspose
 06073                     || !isFullAfbcPipeline
 06074                     || doScaling)
 6075                {
 06076                    var isScaleRatioSupported = IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 6077
 6078                    // RGA3 hardware only support (1/8 ~ 8) scaling in each blit operation,
 6079                    // but in Trickplay there's a case: (3840/320 == 12), enable 2pass for it
 06080                    if (doScaling && !isScaleRatioSupported)
 6081                    {
 6082                        // Vendor provided BSP kernel has an RGA driver bug that causes the output to be corrupted for P
 6083                        // Use NV15 instead of P010 to avoid the issue.
 6084                        // SDR inputs are using BGRA formats already which is not affected.
 06085                        var intermediateFormat = doOclTonemap ? "nv15" : (isMjpegEncoder ? "bgra" : outFormat);
 06086                        var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={intermediateFormat}:force_o
 06087                        mainFilters.Add(hwScaleFilterFirstPass);
 6088                    }
 6089
 6090                    // The RKMPP MJPEG encoder on some newer chip models no longer supports RGB input.
 6091                    // Use 2pass here to enable RGA output of full-range YUV in the 2nd pass.
 06092                    if (isMjpegEncoder && !doOclTonemap && ((doScaling && isScaleRatioSupported) || !doScaling))
 6093                    {
 06094                        var hwScaleFilterFirstPass = "vpp_rkrga=format=bgra:afbc=1";
 06095                        mainFilters.Add(hwScaleFilterFirstPass);
 6096                    }
 6097
 06098                    if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose)
 6099                    {
 06100                        hwScaleFilter += $":transpose={transposeDir}";
 6101                    }
 6102
 6103                    // try enabling AFBC to save DDR bandwidth
 06104                    if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline)
 6105                    {
 06106                        hwScaleFilter += ":afbc=1";
 6107                    }
 6108
 6109                    // hw transpose & scale
 06110                    mainFilters.Add(hwScaleFilter);
 6111                }
 6112            }
 6113
 06114            if (doOclTonemap && isRkmppDecoder)
 6115            {
 6116                // map from rkmpp/drm to opencl via drm-opencl interop.
 06117                mainFilters.Add("hwmap=derive_device=opencl");
 6118            }
 6119
 6120            // ocl tonemap
 06121            if (doOclTonemap)
 6122            {
 06123                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 06124                mainFilters.Add(tonemapFilter);
 6125            }
 6126
 06127            var memoryOutput = false;
 06128            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 06129            if ((isRkmppDecoder && isSwEncoder) || isUploadForOclTonemap)
 6130            {
 06131                memoryOutput = true;
 6132
 6133                // OUTPUT nv12 surface(memory)
 06134                mainFilters.Add("hwdownload");
 06135                mainFilters.Add("format=nv12");
 6136            }
 6137
 6138            // OUTPUT nv12 surface(memory)
 06139            if (isSwDecoder && isRkmppEncoder)
 6140            {
 06141                memoryOutput = true;
 6142            }
 6143
 06144            if (memoryOutput)
 6145            {
 6146                // text subtitles
 06147                if (hasTextSubs)
 6148                {
 06149                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 06150                    mainFilters.Add(textSubtitlesFilter);
 6151                }
 6152            }
 6153
 06154            if (isDrmInDrmOut)
 6155            {
 06156                if (doOclTonemap)
 6157                {
 6158                    // OUTPUT drm(nv12) surface(gem/dma-heap)
 6159                    // reverse-mapping via drm-opencl interop.
 06160                    mainFilters.Add("hwmap=derive_device=rkmpp:reverse=1");
 06161                    mainFilters.Add("format=drm_prime");
 6162                }
 6163            }
 6164
 6165            /* Make sub and overlay filters for subtitle stream */
 06166            var subFilters = new List<string>();
 06167            var overlayFilters = new List<string>();
 06168            if (isDrmInDrmOut)
 6169            {
 06170                if (hasSubs)
 6171                {
 06172                    var subMaxH = 1080;
 06173                    if (hasGraphicalSubs)
 6174                    {
 06175                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 06176                        subFilters.Add(subPreProcFilters);
 06177                        subFilters.Add("format=bgra");
 6178                    }
 06179                    else if (hasTextSubs)
 6180                    {
 06181                        var framerate = state.VideoStream?.RealFrameRate;
 06182                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 6183
 6184                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 06185                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, subMaxH, subF
 06186                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 06187                        subFilters.Add(alphaSrcFilter);
 06188                        subFilters.Add("format=bgra");
 06189                        subFilters.Add(subTextSubtitlesFilter);
 6190                    }
 6191
 06192                    subFilters.Add("hwupload=derive_device=rkmpp");
 6193
 6194                    // offload 1080p+ subtitles swscale upscaling from CPU to RGA
 06195                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 06196                    if (overlayW.HasValue && overlayH.HasValue && overlayH.Value > subMaxH)
 6197                    {
 06198                        subFilters.Add($"vpp_rkrga=w={overlayW.Value}:h={overlayH.Value}:format=bgra:afbc=1");
 6199                    }
 6200
 6201                    // try enabling AFBC to save DDR bandwidth
 06202                    var hwOverlayFilter = "overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12";
 06203                    if (isEncoderSupportAfbc)
 6204                    {
 06205                        hwOverlayFilter += ":afbc=1";
 6206                    }
 6207
 06208                    overlayFilters.Add(hwOverlayFilter);
 6209                }
 6210            }
 06211            else if (memoryOutput)
 6212            {
 06213                if (hasGraphicalSubs)
 6214                {
 06215                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 06216                    subFilters.Add(subPreProcFilters);
 06217                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 6218                }
 6219            }
 6220
 06221            return (mainFilters, subFilters, overlayFilters);
 6222        }
 6223
 6224        /// <summary>
 6225        /// Gets the parameter of video processing filters.
 6226        /// </summary>
 6227        /// <param name="state">Encoding state.</param>
 6228        /// <param name="options">Encoding options.</param>
 6229        /// <param name="outputVideoCodec">Video codec to use.</param>
 6230        /// <returns>The video processing filters parameter.</returns>
 6231        public string GetVideoProcessingFilterParam(
 6232            EncodingJobInfo state,
 6233            EncodingOptions options,
 6234            string outputVideoCodec)
 6235        {
 06236            var videoStream = state.VideoStream;
 06237            if (videoStream is null)
 6238            {
 06239                return string.Empty;
 6240            }
 6241
 06242            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 06243            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 06244            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 6245
 6246            List<string> mainFilters;
 6247            List<string> subFilters;
 6248            List<string> overlayFilters;
 6249
 06250            (mainFilters, subFilters, overlayFilters) = options.HardwareAccelerationType switch
 06251            {
 06252                HardwareAccelerationType.vaapi => GetVaapiVidFilterChain(state, options, outputVideoCodec),
 06253                HardwareAccelerationType.amf => GetAmdVidFilterChain(state, options, outputVideoCodec),
 06254                HardwareAccelerationType.qsv => GetIntelVidFilterChain(state, options, outputVideoCodec),
 06255                HardwareAccelerationType.nvenc => GetNvidiaVidFilterChain(state, options, outputVideoCodec),
 06256                HardwareAccelerationType.videotoolbox => GetAppleVidFilterChain(state, options, outputVideoCodec),
 06257                HardwareAccelerationType.rkmpp => GetRkmppVidFilterChain(state, options, outputVideoCodec),
 06258                _ => GetSwVidFilterChain(state, options, outputVideoCodec),
 06259            };
 6260
 06261            mainFilters?.RemoveAll(string.IsNullOrEmpty);
 06262            subFilters?.RemoveAll(string.IsNullOrEmpty);
 06263            overlayFilters?.RemoveAll(string.IsNullOrEmpty);
 6264
 06265            var framerate = GetFramerateParam(state);
 06266            if (mainFilters is not null && framerate.HasValue)
 6267            {
 06268                var doDeintH2645 = IsDeinterlaceAvailable(state);
 06269                var fpsFilter = string.Format(CultureInfo.InvariantCulture, "fps={0}", framerate.Value);
 6270
 6271                // For filter chain containing the deinterlace filter,
 6272                // place the fps filter at the end to preserve temporal info.
 06273                if (doDeintH2645)
 6274                {
 06275                    mainFilters.Add(fpsFilter);
 6276                }
 6277                else
 6278                {
 06279                    mainFilters.Insert(0, fpsFilter);
 6280                }
 6281            }
 6282
 06283            var mainStr = string.Empty;
 06284            if (mainFilters?.Count > 0)
 6285            {
 06286                mainStr = string.Format(
 06287                    CultureInfo.InvariantCulture,
 06288                    "{0}",
 06289                    string.Join(',', mainFilters));
 6290            }
 6291
 06292            if (overlayFilters?.Count == 0)
 6293            {
 6294                // -vf "scale..."
 06295                return string.IsNullOrEmpty(mainStr) ? string.Empty : " -vf \"" + mainStr + "\"";
 6296            }
 6297
 06298            if (overlayFilters?.Count > 0
 06299                && subFilters?.Count > 0
 06300                && state.SubtitleStream is not null)
 6301            {
 6302                // overlay graphical/text subtitles
 06303                var subStr = string.Format(
 06304                        CultureInfo.InvariantCulture,
 06305                        "{0}",
 06306                        string.Join(',', subFilters));
 6307
 06308                var overlayStr = string.Format(
 06309                        CultureInfo.InvariantCulture,
 06310                        "{0}",
 06311                        string.Join(',', overlayFilters));
 6312
 06313                var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
 06314                var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 06315                var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 6316
 06317                if (hasSubs)
 6318                {
 6319                    // -filter_complex "[0:s]scale=s[sub]..."
 06320                    var filterStr = string.IsNullOrEmpty(mainStr)
 06321                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]{5}\""
 06322                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6323
 06324                    if (hasTextSubs)
 6325                    {
 06326                        filterStr = string.IsNullOrEmpty(mainStr)
 06327                            ? " -filter_complex \"{4}[sub];[0:{2}][sub]{5}\""
 06328                            : " -filter_complex \"{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6329                    }
 6330
 06331                    return string.Format(
 06332                        CultureInfo.InvariantCulture,
 06333                        filterStr,
 06334                        mapPrefix,
 06335                        subtitleStreamIndex,
 06336                        videoStreamIndex,
 06337                        mainStr,
 06338                        subStr,
 06339                        overlayStr);
 6340                }
 6341            }
 6342
 06343            return string.Empty;
 6344        }
 6345
 6346        public string GetOverwriteColorPropertiesParam(EncodingJobInfo state, bool isTonemapAvailable)
 6347        {
 06348            if (isTonemapAvailable)
 6349            {
 06350                return GetInputHdrParam(state.VideoStream?.ColorTransfer);
 6351            }
 6352
 06353            return GetOutputSdrParam(null);
 6354        }
 6355
 6356        public string GetInputHdrParam(string colorTransfer)
 6357        {
 06358            if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
 6359            {
 6360                // HLG
 06361                return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
 6362            }
 6363
 6364            // HDR10
 06365            return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc";
 6366        }
 6367
 6368        public string GetOutputSdrParam(string tonemappingRange)
 6369        {
 6370            // SDR
 06371            if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
 6372            {
 06373                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
 6374            }
 6375
 06376            if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
 6377            {
 06378                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc";
 6379            }
 6380
 06381            return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
 6382        }
 6383
 6384        public static int GetVideoColorBitDepth(EncodingJobInfo state)
 6385        {
 06386            var videoStream = state.VideoStream;
 06387            if (videoStream is not null)
 6388            {
 06389                if (videoStream.BitDepth.HasValue)
 6390                {
 06391                    return videoStream.BitDepth.Value;
 6392                }
 6393
 06394                if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
 06395                    || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
 06396                    || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 06397                    || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
 6398                {
 06399                    return 8;
 6400                }
 6401
 06402                if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 06403                    || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 06404                    || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
 6405                {
 06406                    return 10;
 6407                }
 6408
 06409                if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 06410                    || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 06411                    || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
 6412                {
 06413                    return 12;
 6414                }
 6415
 06416                return 8;
 6417            }
 6418
 06419            return 0;
 6420        }
 6421
 6422        /// <summary>
 6423        /// Gets the ffmpeg option string for the hardware accelerated video decoder.
 6424        /// </summary>
 6425        /// <param name="state">The encoding job info.</param>
 6426        /// <param name="options">The encoding options.</param>
 6427        /// <returns>The option string or null if none available.</returns>
 6428        protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
 6429        {
 46430            var videoStream = state.VideoStream;
 46431            var mediaSource = state.MediaSource;
 46432            if (videoStream is null || mediaSource is null)
 6433            {
 06434                return null;
 6435            }
 6436
 6437            // HWA decoders can handle both video files and video folders.
 46438            var videoType = state.VideoType;
 46439            if (videoType != VideoType.VideoFile
 46440                && videoType != VideoType.Iso
 46441                && videoType != VideoType.Dvd
 46442                && videoType != VideoType.BluRay)
 6443            {
 06444                return null;
 6445            }
 6446
 46447            if (IsCopyCodec(state.OutputVideoCodec))
 6448            {
 06449                return null;
 6450            }
 6451
 46452            var hardwareAccelerationType = options.HardwareAccelerationType;
 6453
 46454            if (!string.IsNullOrEmpty(videoStream.Codec) && hardwareAccelerationType != HardwareAccelerationType.none)
 6455            {
 06456                var bitDepth = GetVideoColorBitDepth(state);
 6457
 6458                // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support for most platforms
 06459                if (bitDepth == 10
 06460                    && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06461                         || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
 06462                         || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)
 06463                         || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)))
 6464                {
 6465                    // RKMPP has H.264 Hi10P decoder
 06466                    bool hasHardwareHi10P = hardwareAccelerationType == HardwareAccelerationType.rkmpp;
 6467
 6468                    // VideoToolbox on Apple Silicon has H.264 Hi10P mode enabled after macOS 14.6
 06469                    if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox)
 6470                    {
 06471                        var ver = Environment.OSVersion.Version;
 06472                        var arch = RuntimeInformation.OSArchitecture;
 06473                        if (arch.Equals(Architecture.Arm64) && ver >= new Version(14, 6))
 6474                        {
 06475                            hasHardwareHi10P = true;
 6476                        }
 6477                    }
 6478
 06479                    if (!hasHardwareHi10P
 06480                        && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6481                    {
 06482                        return null;
 6483                    }
 6484                }
 6485
 6486                // Block unsupported H.264 Hi422P and Hi444PP profiles, which can be encoded with 4:2:0 pixel format
 06487                if (string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)
 06488                    && ((videoStream.Profile?.Contains("4:2:2", StringComparison.OrdinalIgnoreCase) ?? false)
 06489                        || (videoStream.Profile?.Contains("4:4:4", StringComparison.OrdinalIgnoreCase) ?? false)))
 6490                {
 6491                    // VideoToolbox on Apple Silicon has H.264 Hi444PP and theoretically also has Hi422P
 06492                    if (!(hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06493                          && RuntimeInformation.OSArchitecture.Equals(Architecture.Arm64)))
 6494                    {
 06495                        return null;
 6496                    }
 6497                }
 6498
 06499                var decoder = hardwareAccelerationType switch
 06500                {
 06501                    HardwareAccelerationType.vaapi => GetVaapiVidDecoder(state, options, videoStream, bitDepth),
 06502                    HardwareAccelerationType.amf => GetAmfVidDecoder(state, options, videoStream, bitDepth),
 06503                    HardwareAccelerationType.qsv => GetQsvHwVidDecoder(state, options, videoStream, bitDepth),
 06504                    HardwareAccelerationType.nvenc => GetNvdecVidDecoder(state, options, videoStream, bitDepth),
 06505                    HardwareAccelerationType.videotoolbox => GetVideotoolboxVidDecoder(state, options, videoStream, bitD
 06506                    HardwareAccelerationType.rkmpp => GetRkmppVidDecoder(state, options, videoStream, bitDepth),
 06507                    _ => string.Empty
 06508                };
 6509
 06510                if (!string.IsNullOrEmpty(decoder))
 6511                {
 06512                    return decoder;
 6513                }
 6514            }
 6515
 6516            // leave blank so ffmpeg will decide
 46517            return null;
 6518        }
 6519
 6520        /// <summary>
 6521        /// Gets a hw decoder name.
 6522        /// </summary>
 6523        /// <param name="options">Encoding options.</param>
 6524        /// <param name="decoderPrefix">Decoder prefix.</param>
 6525        /// <param name="decoderSuffix">Decoder suffix.</param>
 6526        /// <param name="videoCodec">Video codec to use.</param>
 6527        /// <param name="bitDepth">Video color bit depth.</param>
 6528        /// <returns>Hardware decoder name.</returns>
 6529        public string GetHwDecoderName(EncodingOptions options, string decoderPrefix, string decoderSuffix, string video
 6530        {
 06531            if (string.IsNullOrEmpty(decoderPrefix) || string.IsNullOrEmpty(decoderSuffix))
 6532            {
 06533                return null;
 6534            }
 6535
 06536            var decoderName = decoderPrefix + '_' + decoderSuffix;
 6537
 06538            var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains
 6539
 6540            // VideoToolbox decoders have built-in SW fallback
 06541            if (bitDepth == 10
 06542                && isCodecAvailable
 06543                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6544            {
 06545                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06546                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
 06547                    && !options.EnableDecodingColorDepth10Hevc)
 6548                {
 06549                    return null;
 6550                }
 6551
 06552                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06553                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06554                    && !options.EnableDecodingColorDepth10Vp9)
 6555                {
 06556                    return null;
 6557                }
 6558            }
 6559
 06560            if (string.Equals(decoderSuffix, "cuvid", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdec
 6561            {
 06562                return null;
 6563            }
 6564
 06565            if (string.Equals(decoderSuffix, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwD
 6566            {
 06567                return null;
 6568            }
 6569
 06570            if (string.Equals(decoderSuffix, "rkmpp", StringComparison.OrdinalIgnoreCase))
 6571            {
 06572                return null;
 6573            }
 6574
 06575            return isCodecAvailable ? (" -c:v " + decoderName) : null;
 6576        }
 6577
 6578        /// <summary>
 6579        /// Gets a hwaccel type to use as a hardware decoder depending on the system.
 6580        /// </summary>
 6581        /// <param name="state">Encoding state.</param>
 6582        /// <param name="options">Encoding options.</param>
 6583        /// <param name="videoCodec">Video codec to use.</param>
 6584        /// <param name="bitDepth">Video color bit depth.</param>
 6585        /// <param name="outputHwSurface">Specifies if output hw surface.</param>
 6586        /// <returns>Hardware accelerator type.</returns>
 6587        public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, int bitDepth, bo
 6588        {
 06589            var isWindows = OperatingSystem.IsWindows();
 06590            var isLinux = OperatingSystem.IsLinux();
 06591            var isMacOS = OperatingSystem.IsMacOS();
 06592            var isD3d11Supported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va");
 06593            var isVaapiSupported = isLinux && IsVaapiSupported(state);
 06594            var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported();
 06595            var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv");
 06596            var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
 06597            var isRkmppSupported = isLinux && IsRkmppFullSupported();
 06598            var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCas
 06599            var hardwareAccelerationType = options.HardwareAccelerationType;
 6600
 06601            var ffmpegVersion = _mediaEncoder.EncoderVersion;
 6602
 6603            // Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
 06604            var isAv1 = ffmpegVersion < _minFFmpegImplicitHwaccel
 06605                && string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
 6606
 6607            // Allow profile mismatch if decoding H.264 baseline with d3d11va and vaapi hwaccels.
 06608            var profileMismatch = string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
 06609                && string.Equals(state.VideoStream?.Profile, "baseline", StringComparison.OrdinalIgnoreCase);
 6610
 6611            // Disable the extra internal copy in nvdec. We already handle it in filter chain.
 06612            var nvdecNoInternalCopy = ffmpegVersion >= _minFFmpegHwaUnsafeOutput;
 6613
 6614            // Strip the display rotation side data from the transposed fmp4 output stream.
 06615            var stripRotationData = (state.VideoStream?.Rotation ?? 0) != 0
 06616                && ffmpegVersion >= _minFFmpegDisplayRotationOption;
 06617            var stripRotationDataArgs = stripRotationData ? " -display_rotation 0" : string.Empty;
 6618
 6619            // VideoToolbox decoders have built-in SW fallback
 06620            if (isCodecAvailable
 06621                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6622            {
 06623                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06624                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase))
 6625                {
 06626                    if (IsVideoStreamHevcRext(state))
 6627                    {
 06628                        if (bitDepth <= 10 && !options.EnableDecodingColorDepth10HevcRext)
 6629                        {
 06630                            return null;
 6631                        }
 6632
 06633                        if (bitDepth == 12 && !options.EnableDecodingColorDepth12HevcRext)
 6634                        {
 06635                            return null;
 6636                        }
 6637
 06638                        if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06639                            && !_mediaEncoder.IsVaapiDeviceInteliHD)
 6640                        {
 06641                            return null;
 6642                        }
 6643                    }
 06644                    else if (bitDepth == 10 && !options.EnableDecodingColorDepth10Hevc)
 6645                    {
 06646                        return null;
 6647                    }
 6648                }
 6649
 06650                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06651                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06652                    && bitDepth == 10
 06653                    && !options.EnableDecodingColorDepth10Vp9)
 6654                {
 06655                    return null;
 6656                }
 6657            }
 6658
 6659            // Intel qsv/d3d11va/vaapi
 06660            if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 6661            {
 06662                if (options.PreferSystemNativeHwDecoder)
 6663                {
 06664                    if (isVaapiSupported && isCodecAvailable)
 6665                    {
 06666                        return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + st
 06667                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " 
 6668                    }
 6669
 06670                    if (isD3d11Supported && isCodecAvailable)
 6671                    {
 06672                        return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + 
 06673                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 
 6674                    }
 6675                }
 6676                else
 6677                {
 06678                    if (isQsvSupported && isCodecAvailable)
 6679                    {
 06680                        return " -hwaccel qsv" + (outputHwSurface ? " -hwaccel_output_format qsv -noautorotate" + stripR
 6681                    }
 6682                }
 6683            }
 6684
 6685            // Nvidia cuda
 06686            if (hardwareAccelerationType == HardwareAccelerationType.nvenc)
 6687            {
 06688                if (isCudaSupported && isCodecAvailable)
 6689                {
 06690                    if (options.EnableEnhancedNvdecDecoder)
 6691                    {
 6692                        // set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support.
 06693                        return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stri
 06694                            + (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + 
 6695                    }
 6696
 6697                    // cuvid decoder doesn't have threading issue.
 06698                    return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stripRot
 6699                }
 6700            }
 6701
 6702            // Amd d3d11va
 06703            if (hardwareAccelerationType == HardwareAccelerationType.amf)
 6704            {
 06705                if (isD3d11Supported && isCodecAvailable)
 6706                {
 06707                    return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + stri
 06708                        + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 2" +
 6709                }
 6710            }
 6711
 6712            // Vaapi
 06713            if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06714                && isVaapiSupported
 06715                && isCodecAvailable)
 6716            {
 06717                return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + stripRotat
 06718                    + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1
 6719            }
 6720
 6721            // Apple videotoolbox
 06722            if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06723                && isVideotoolboxSupported
 06724                && isCodecAvailable)
 6725            {
 06726                return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string
 6727            }
 6728
 6729            // Rockchip rkmpp
 06730            if (hardwareAccelerationType == HardwareAccelerationType.rkmpp
 06731                && isRkmppSupported
 06732                && isCodecAvailable)
 6733            {
 06734                return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime -noautorotate" + stripR
 6735            }
 6736
 06737            return null;
 6738        }
 6739
 6740        public string GetQsvHwVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6741        {
 06742            var isWindows = OperatingSystem.IsWindows();
 06743            var isLinux = OperatingSystem.IsLinux();
 6744
 06745            if ((!isWindows && !isLinux)
 06746                || options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 6747            {
 06748                return null;
 6749            }
 6750
 06751            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 06752            var isIntelDx11OclSupported = isWindows
 06753                && _mediaEncoder.SupportsHwaccel("d3d11va")
 06754                && isQsvOclSupported;
 06755            var isIntelVaapiOclSupported = isLinux
 06756                && IsVaapiSupported(state)
 06757                && isQsvOclSupported;
 06758            var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
 06759                && _mediaEncoder.SupportsFilter("alphasrc");
 6760
 06761            var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06762                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06763            var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 06764            var is8_10_12bitSwFormatsQsv = is8_10bitSwFormatsQsv
 06765                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06766                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06767                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06768                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06769                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06770                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06771                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6772            // TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
 6773
 06774            if (is8bitSwFormatsQsv)
 6775            {
 06776                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06777                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6778                {
 06779                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6780                }
 6781
 06782                if (string.Equals(videoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase))
 6783                {
 06784                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6785                }
 6786
 06787                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6788                {
 06789                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6790                }
 6791
 06792                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6793                {
 06794                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6795                }
 6796            }
 6797
 06798            if (is8_10bitSwFormatsQsv)
 6799            {
 06800                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6801                {
 06802                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6803                }
 6804
 06805                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 6806                {
 06807                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6808                }
 6809            }
 6810
 06811            if (is8_10_12bitSwFormatsQsv)
 6812            {
 06813                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06814                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6815                {
 06816                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6817                }
 6818            }
 6819
 06820            return null;
 6821        }
 6822
 6823        public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6824        {
 06825            if ((!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
 06826                || options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 6827            {
 06828                return null;
 6829            }
 6830
 06831            var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
 06832            var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06833                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06834            var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06835            var is8_10_12bitSwFormatsNvdec = is8_10bitSwFormatsNvdec
 06836                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06837                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06838                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06839                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6840            // TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
 6841
 06842            if (is8bitSwFormatsNvdec)
 6843            {
 06844                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06845                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6846                {
 06847                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6848                }
 6849
 06850                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6851                {
 06852                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6853                }
 6854
 06855                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6856                {
 06857                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6858                }
 6859
 06860                if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6861                {
 06862                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpe
 6863                }
 6864
 06865                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6866                {
 06867                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6868                }
 6869            }
 6870
 06871            if (is8_10bitSwFormatsNvdec)
 6872            {
 06873                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6874                {
 06875                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6876                }
 6877
 06878                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6879                {
 06880                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6881                }
 6882            }
 6883
 06884            if (is8_10_12bitSwFormatsNvdec)
 6885            {
 06886                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06887                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6888                {
 06889                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6890                }
 6891            }
 6892
 06893            return null;
 6894        }
 6895
 6896        public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitD
 6897        {
 06898            if (!OperatingSystem.IsWindows()
 06899                || options.HardwareAccelerationType != HardwareAccelerationType.amf)
 6900            {
 06901                return null;
 6902            }
 6903
 06904            var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
 06905                && IsOpenclFullSupported()
 06906                && _mediaEncoder.SupportsFilter("alphasrc");
 06907            var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06908                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06909            var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 6910
 06911            if (is8bitSwFormatsAmf)
 6912            {
 06913                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06914                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6915                {
 06916                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6917                }
 6918
 06919                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6920                {
 06921                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6922                }
 6923
 06924                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6925                {
 06926                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6927                }
 6928            }
 6929
 06930            if (is8_10bitSwFormatsAmf)
 6931            {
 06932                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06933                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6934                {
 06935                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6936                }
 6937
 06938                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6939                {
 06940                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6941                }
 6942
 06943                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6944                {
 06945                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6946                }
 6947            }
 6948
 06949            return null;
 6950        }
 6951
 6952        public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6953        {
 06954            if (!OperatingSystem.IsLinux()
 06955                || options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 6956            {
 06957                return null;
 6958            }
 6959
 06960            var hwSurface = IsVaapiSupported(state)
 06961                && IsVaapiFullSupported()
 06962                && IsOpenclFullSupported()
 06963                && _mediaEncoder.SupportsFilter("alphasrc");
 06964            var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06965                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06966            var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06967            var is8_10_12bitSwFormatsVaapi = is8_10bitSwFormatsVaapi
 06968                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06969                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06970                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06971                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06972                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06973                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06974                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6975
 06976            if (is8bitSwFormatsVaapi)
 6977            {
 06978                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06979                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6980                {
 06981                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6982                }
 6983
 06984                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6985                {
 06986                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6987                }
 6988
 06989                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6990                {
 06991                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6992                }
 6993
 06994                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6995                {
 06996                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6997                }
 6998            }
 6999
 07000            if (is8_10bitSwFormatsVaapi)
 7001            {
 07002                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7003                {
 07004                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 7005                }
 7006
 07007                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7008                {
 07009                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 7010                }
 7011            }
 7012
 07013            if (is8_10_12bitSwFormatsVaapi)
 7014            {
 07015                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 07016                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7017                {
 07018                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 7019                }
 7020            }
 7021
 07022            return null;
 7023        }
 7024
 7025        public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream,
 7026        {
 07027            if (!OperatingSystem.IsMacOS()
 07028                || options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 7029            {
 07030                return null;
 7031            }
 7032
 07033            var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase
 07034                                    || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnore
 07035            var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, String
 07036            var is8_10_12bitSwFormatsVt = is8_10bitSwFormatsVt
 07037                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07038                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07039                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07040                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07041                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07042                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 07043                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 07044            var isAv1SupportedSwFormatsVt = is8_10bitSwFormatsVt || string.Equals("yuv420p12le", videoStream.PixelFormat
 7045
 7046            // The related patches make videotoolbox hardware surface working is only available in jellyfin-ffmpeg 7.0.1
 07047            bool useHwSurface = (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface) && IsVideoToolboxFullSupp
 7048
 07049            if (is8bitSwFormatsVt)
 7050            {
 07051                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7052                {
 07053                    return GetHwaccelType(state, options, "vp8", bitDepth, useHwSurface);
 7054                }
 7055            }
 7056
 07057            if (is8_10bitSwFormatsVt)
 7058            {
 07059                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 07060                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7061                {
 07062                    return GetHwaccelType(state, options, "h264", bitDepth, useHwSurface);
 7063                }
 7064
 07065                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7066                {
 07067                    return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
 7068                }
 7069            }
 7070
 07071            if (is8_10_12bitSwFormatsVt)
 7072            {
 07073                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 07074                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 7075                {
 07076                    return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
 7077                }
 7078
 07079                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 07080                    && isAv1SupportedSwFormatsVt
 07081                    && _mediaEncoder.IsVideoToolboxAv1DecodeAvailable)
 7082                {
 07083                    return GetHwaccelType(state, options, "av1", bitDepth, useHwSurface);
 7084                }
 7085            }
 7086
 07087            return null;
 7088        }
 7089
 7090        public string GetRkmppVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 7091        {
 07092            var isLinux = OperatingSystem.IsLinux();
 7093
 07094            if (!isLinux
 07095                || options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 7096            {
 07097                return null;
 7098            }
 7099
 07100            var inW = state.VideoStream?.Width;
 07101            var inH = state.VideoStream?.Height;
 07102            var reqW = state.BaseRequest.Width;
 07103            var reqH = state.BaseRequest.Height;
 07104            var reqMaxW = state.BaseRequest.MaxWidth;
 07105            var reqMaxH = state.BaseRequest.MaxHeight;
 7106
 7107            // rkrga RGA2e supports range from 1/16 to 16
 07108            if (!IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 16.0f))
 7109            {
 07110                return null;
 7111            }
 7112
 07113            var isRkmppOclSupported = IsRkmppFullSupported() && IsOpenclFullSupported();
 07114            var hwSurface = isRkmppOclSupported
 07115                && _mediaEncoder.SupportsFilter("alphasrc");
 7116
 7117            // rkrga RGA3 supports range from 1/8 to 8
 07118            var isAfbcSupported = hwSurface && IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 7119
 7120            // TODO: add more 8/10bit and 4:2:2 formats for Rkmpp after finishing the ffcheck tool
 07121            var is8bitSwFormatsRkmpp = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 07122                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 07123            var is10bitSwFormatsRkmpp = string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIg
 07124            var is8_10bitSwFormatsRkmpp = is8bitSwFormatsRkmpp || is10bitSwFormatsRkmpp;
 7125
 7126            // nv15 and nv20 are bit-stream only formats
 07127            if (is10bitSwFormatsRkmpp && !hwSurface)
 7128            {
 07129                return null;
 7130            }
 7131
 07132            if (is8bitSwFormatsRkmpp)
 7133            {
 07134                if (string.Equals(videoStream.Codec, "mpeg1video", StringComparison.OrdinalIgnoreCase))
 7135                {
 07136                    return GetHwaccelType(state, options, "mpeg1video", bitDepth, hwSurface);
 7137                }
 7138
 07139                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 7140                {
 07141                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 7142                }
 7143
 07144                if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 7145                {
 07146                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
 7147                }
 7148
 07149                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 7150                {
 07151                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 7152                }
 7153            }
 7154
 07155            if (is8_10bitSwFormatsRkmpp)
 7156            {
 07157                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 07158                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 7159                {
 07160                    var accelType = GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 07161                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7162                }
 7163
 07164                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 07165                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 7166                {
 07167                    var accelType = GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 07168                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7169                }
 7170
 07171                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 7172                {
 07173                    var accelType = GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 07174                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7175                }
 7176
 07177                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 7178                {
 7179                    // there's an issue about AV1 AFBC on RK3588, disable it for now until it's fixed upstream
 07180                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 7181                }
 7182            }
 7183
 07184            return null;
 7185        }
 7186
 7187        /// <summary>
 7188        /// Gets the number of threads.
 7189        /// </summary>
 7190        /// <param name="state">Encoding state.</param>
 7191        /// <param name="encodingOptions">Encoding options.</param>
 7192        /// <param name="outputVideoCodec">Video codec to use.</param>
 7193        /// <returns>Number of threads.</returns>
 7194#nullable enable
 7195        public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVide
 7196        {
 07197            var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount;
 7198
 07199            if (threads <= 0)
 7200            {
 7201                // Automatically set thread count
 07202                return 0;
 7203            }
 7204
 07205            return Math.Min(threads, Environment.ProcessorCount);
 7206        }
 7207
 7208#nullable disable
 7209        public void TryStreamCopy(EncodingJobInfo state, EncodingOptions options)
 7210        {
 07211            if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream))
 7212            {
 07213                state.OutputVideoCodec = "copy";
 7214            }
 7215            else
 7216            {
 07217                var user = state.User;
 7218
 7219                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07220                if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
 7221                {
 07222                    state.OutputVideoCodec = "copy";
 7223                }
 7224            }
 7225
 07226            var preventHlsAudioCopy = state.TranscodingType is TranscodingJobType.Hls
 07227                && state.VideoStream is not null
 07228                && !IsCopyCodec(state.OutputVideoCodec)
 07229                && options.HlsAudioSeekStrategy is HlsAudioSeekStrategy.TranscodeAudio;
 7230
 07231            TranscodeReason audioCopyFailureReasons = 0;
 07232            if (state.AudioStream is not null
 07233                && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs, out audioCopyFailureReasons)
 07234                && !preventHlsAudioCopy)
 7235            {
 07236                state.OutputAudioCodec = "copy";
 7237            }
 7238            else
 7239            {
 07240                var user = state.User;
 7241
 7242                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07243                if (user is not null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
 7244                {
 07245                    state.OutputAudioCodec = "copy";
 7246                }
 07247                else if (state.AudioStream is not null && !IsCopyCodec(state.OutputAudioCodec))
 7248                {
 7249                    // Audio is actually being re-encoded although the playback determination may have considered the so
 7250                    // Only carry the primary "cannot be passed through" cause - the codec mismatch.
 7251                    // Bitrate/channels/sample-rate/bit-depth copy refusals are consequences of the chosen transcode tar
 07252                    state.AddTranscodeReason(audioCopyFailureReasons & TranscodeReason.AudioCodecNotSupported);
 7253                }
 7254            }
 07255        }
 7256
 7257        private string GetFfmpegAnalyzeDurationArg(EncodingJobInfo state)
 7258        {
 47259            var analyzeDurationArgument = string.Empty;
 7260
 7261            // Apply -analyzeduration as per the environment variable,
 7262            // otherwise ffmpeg will break on certain files due to default value is 0.
 47263            var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
 7264
 47265            if (state.MediaSource.AnalyzeDurationMs > 0)
 7266            {
 07267                analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToStr
 7268            }
 47269            else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
 7270            {
 07271                analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
 7272            }
 7273
 47274            return analyzeDurationArgument;
 7275        }
 7276
 7277        private string GetFfmpegProbesizeArg()
 7278        {
 47279            var ffmpegProbeSize = _config.GetFFmpegProbeSize();
 7280
 47281            if (!string.IsNullOrEmpty(ffmpegProbeSize))
 7282            {
 07283                return $"-probesize {ffmpegProbeSize}";
 7284            }
 7285
 47286            return string.Empty;
 7287        }
 7288
 7289        public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
 7290        {
 07291            var inputModifier = string.Empty;
 07292            var analyzeDurationArgument = GetFfmpegAnalyzeDurationArg(state);
 7293
 07294            if (!string.IsNullOrEmpty(analyzeDurationArgument))
 7295            {
 07296                inputModifier += " " + analyzeDurationArgument;
 7297            }
 7298
 07299            inputModifier = inputModifier.Trim();
 7300
 7301            // Apply -probesize if configured
 07302            var ffmpegProbeSizeArgument = GetFfmpegProbesizeArg();
 7303
 07304            if (!string.IsNullOrEmpty(ffmpegProbeSizeArgument))
 7305            {
 07306                inputModifier += " " + ffmpegProbeSizeArgument;
 7307            }
 7308
 07309            var userAgentParam = GetUserAgentParam(state);
 7310
 07311            if (!string.IsNullOrEmpty(userAgentParam))
 7312            {
 07313                inputModifier += " " + userAgentParam;
 7314            }
 7315
 07316            inputModifier = inputModifier.Trim();
 7317
 07318            var refererParam = GetRefererParam(state);
 7319
 07320            if (!string.IsNullOrEmpty(refererParam))
 7321            {
 07322                inputModifier += " " + refererParam;
 7323            }
 7324
 07325            inputModifier = inputModifier.Trim();
 7326
 07327            inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions, segmentContainer);
 07328            inputModifier = inputModifier.Trim();
 7329
 07330            if (state.InputProtocol == MediaProtocol.Rtsp)
 7331            {
 07332                inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
 7333            }
 7334
 07335            if (!string.IsNullOrEmpty(state.InputAudioSync))
 7336            {
 07337                inputModifier += " -async " + state.InputAudioSync;
 7338            }
 7339
 7340            // The -fps_mode option cannot be applied to input
 07341            if (!string.IsNullOrEmpty(state.InputVideoSync) && _mediaEncoder.EncoderVersion < new Version(5, 1))
 7342            {
 07343                inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion);
 7344            }
 7345
 07346            int readrate = 0;
 07347            if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp)
 7348            {
 07349                readrate = 1;
 07350                inputModifier += " -re";
 7351            }
 07352            else if (encodingOptions.EnableSegmentDeletion
 07353                && state.VideoStream is not null
 07354                && state.TranscodingType == TranscodingJobType.Hls
 07355                && IsCopyCodec(state.OutputVideoCodec)
 07356                && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateOption)
 7357            {
 7358                // Set an input read rate limit 10x for using SegmentDeletion with stream-copy
 7359                // to prevent ffmpeg from exiting prematurely (due to fast drive)
 07360                readrate = 10;
 07361                inputModifier += $" -readrate {readrate}";
 7362            }
 7363
 7364            // Set a larger catchup value to revert to the old behavior,
 7365            // otherwise, remuxing might stall due to this new option
 07366            if (readrate > 0 && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateCatchupOption)
 7367            {
 07368                inputModifier += $" -readrate_catchup {readrate * 100}";
 7369            }
 7370
 07371            var flags = new List<string>();
 07372            if (state.IgnoreInputDts)
 7373            {
 07374                flags.Add("+igndts");
 7375            }
 7376
 07377            if (state.IgnoreInputIndex)
 7378            {
 07379                flags.Add("+ignidx");
 7380            }
 7381
 07382            if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
 7383            {
 07384                flags.Add("+genpts");
 7385            }
 7386
 07387            if (state.DiscardCorruptFramesInput)
 7388            {
 07389                flags.Add("+discardcorrupt");
 7390            }
 7391
 07392            if (state.EnableFastSeekInput)
 7393            {
 07394                flags.Add("+fastseek");
 7395            }
 7396
 07397            if (flags.Count > 0)
 7398            {
 07399                inputModifier += " -fflags " + string.Join(string.Empty, flags);
 7400            }
 7401
 07402            if (state.IsVideoRequest)
 7403            {
 07404                if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && encodingOpt
 7405                {
 07406                    var inputFormat = GetInputFormat(state.InputContainer);
 07407                    if (!string.IsNullOrEmpty(inputFormat))
 7408                    {
 07409                        inputModifier += " -f " + inputFormat;
 7410                    }
 7411                }
 7412            }
 7413
 07414            if (state.MediaSource.RequiresLooping)
 7415            {
 07416                inputModifier += " -stream_loop -1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2";
 7417            }
 7418
 07419            return inputModifier;
 7420        }
 7421
 7422        public void AttachMediaSourceInfo(
 7423            EncodingJobInfo state,
 7424            EncodingOptions encodingOptions,
 7425            MediaSourceInfo mediaSource,
 7426            string requestedUrl)
 7427        {
 07428            ArgumentNullException.ThrowIfNull(state);
 7429
 07430            ArgumentNullException.ThrowIfNull(mediaSource);
 7431
 07432            var path = mediaSource.Path;
 07433            var protocol = mediaSource.Protocol;
 7434
 07435            if (!string.IsNullOrEmpty(mediaSource.EncoderPath) && mediaSource.EncoderProtocol.HasValue)
 7436            {
 07437                path = mediaSource.EncoderPath;
 07438                protocol = mediaSource.EncoderProtocol.Value;
 7439            }
 7440
 07441            state.MediaPath = path;
 07442            state.InputProtocol = protocol;
 07443            state.InputContainer = mediaSource.Container;
 07444            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07445            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 7446
 07447            state.IsoType = mediaSource.IsoType;
 7448
 07449            if (mediaSource.Timestamp.HasValue)
 7450            {
 07451                state.InputTimestamp = mediaSource.Timestamp.Value;
 7452            }
 7453
 07454            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07455            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 07456            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
 7457
 07458            if ((state.ReadInputAtNativeFramerate && !state.IsSegmentedLiveStream)
 07459                || (mediaSource.Protocol == MediaProtocol.File
 07460                && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)))
 7461            {
 07462                state.InputVideoSync = "-1";
 07463                state.InputAudioSync = "1";
 7464            }
 7465
 07466            if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase)
 07467                || string.Equals(mediaSource.Container, "asf", StringComparison.OrdinalIgnoreCase))
 7468            {
 7469                // Seeing some stuttering when transcoding wma to audio-only HLS
 07470                state.InputAudioSync = "1";
 7471            }
 7472
 07473            var mediaStreams = mediaSource.MediaStreams;
 7474
 07475            if (state.IsVideoRequest)
 7476            {
 07477                var videoRequest = state.BaseRequest;
 7478
 07479                if (string.IsNullOrEmpty(videoRequest.VideoCodec))
 7480                {
 07481                    if (string.IsNullOrEmpty(requestedUrl))
 7482                    {
 07483                        requestedUrl = "test." + videoRequest.Container;
 7484                    }
 7485
 07486                    videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
 7487                }
 7488
 07489                state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
 07490                state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Su
 07491                state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
 07492                state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
 7493
 07494                if (state.SubtitleStream is not null && !state.SubtitleStream.IsExternal)
 7495                {
 07496                    state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !
 7497                }
 7498
 07499                EnforceResolutionLimit(state);
 7500
 07501                NormalizeSubtitleEmbed(state);
 7502            }
 7503            else
 7504            {
 07505                state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
 7506            }
 7507
 07508            state.MediaSource = mediaSource;
 7509
 07510            var request = state.BaseRequest;
 07511            var supportedAudioCodecs = state.SupportedAudioCodecs;
 07512            if (request is not null && supportedAudioCodecs is not null && supportedAudioCodecs.Length > 0)
 7513            {
 07514                var supportedAudioCodecsList = supportedAudioCodecs.ToList();
 7515
 07516                ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
 7517
 07518                state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray();
 7519
 07520                request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(_mediaEncoder.CanEncodeToAudioCodec)
 07521                    ?? state.SupportedAudioCodecs.FirstOrDefault();
 7522            }
 7523
 07524            var supportedVideoCodecs = state.SupportedVideoCodecs;
 07525            if (request is not null && supportedVideoCodecs is not null && supportedVideoCodecs.Length > 0)
 7526            {
 07527                var supportedVideoCodecsList = supportedVideoCodecs.ToList();
 7528
 07529                ShiftVideoCodecsIfNeeded(supportedVideoCodecsList, encodingOptions);
 7530
 07531                state.SupportedVideoCodecs = supportedVideoCodecsList.ToArray();
 7532
 07533                request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
 7534            }
 07535        }
 7536
 7537        private void ShiftAudioCodecsIfNeeded(List<string> audioCodecs, MediaStream audioStream)
 7538        {
 7539            // No need to shift if there is only one supported audio codec.
 07540            if (audioCodecs.Count < 2)
 7541            {
 07542                return;
 7543            }
 7544
 07545            var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
 07546            var shiftAudioCodecs = new List<string>();
 07547            if (inputChannels >= 6)
 7548            {
 7549                // DTS and TrueHD are not supported by HLS
 7550                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07551                shiftAudioCodecs.Add("dts");
 07552                shiftAudioCodecs.Add("truehd");
 7553            }
 7554            else
 7555            {
 7556                // Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
 7557                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07558                shiftAudioCodecs.Add("ac3");
 07559                shiftAudioCodecs.Add("eac3");
 7560            }
 7561
 07562            if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7563            {
 07564                return;
 7565            }
 7566
 07567            while (shiftAudioCodecs.Contains(audioCodecs[0], StringComparison.OrdinalIgnoreCase))
 7568            {
 07569                var removed = audioCodecs[0];
 07570                audioCodecs.RemoveAt(0);
 07571                audioCodecs.Add(removed);
 7572            }
 07573        }
 7574
 7575        private void ShiftVideoCodecsIfNeeded(List<string> videoCodecs, EncodingOptions encodingOptions)
 7576        {
 7577            // No need to shift if there is only one supported video codec.
 07578            if (videoCodecs.Count < 2)
 7579            {
 07580                return;
 7581            }
 7582
 7583            // Shift codecs to the end of list if it's not allowed.
 07584            var shiftVideoCodecs = new List<string>();
 07585            if (!encodingOptions.AllowHevcEncoding)
 7586            {
 07587                shiftVideoCodecs.Add("hevc");
 07588                shiftVideoCodecs.Add("h265");
 7589            }
 7590
 07591            if (!encodingOptions.AllowAv1Encoding)
 7592            {
 07593                shiftVideoCodecs.Add("av1");
 7594            }
 7595
 07596            if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7597            {
 07598                return;
 7599            }
 7600
 07601            while (shiftVideoCodecs.Contains(videoCodecs[0], StringComparison.OrdinalIgnoreCase))
 7602            {
 07603                var removed = videoCodecs[0];
 07604                videoCodecs.RemoveAt(0);
 07605                videoCodecs.Add(removed);
 7606            }
 07607        }
 7608
 7609        private void NormalizeSubtitleEmbed(EncodingJobInfo state)
 7610        {
 07611            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7612            {
 07613                return;
 7614            }
 7615
 7616            // This is tricky to remux in, after converting to dvdsub it's not positioned correctly
 7617            // Therefore, let's just burn it in
 07618            if (string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 7619            {
 07620                state.SubtitleDeliveryMethod = SubtitleDeliveryMethod.Encode;
 7621            }
 07622        }
 7623
 7624        public string GetSubtitleEmbedArguments(EncodingJobInfo state)
 7625        {
 07626            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7627            {
 07628                return string.Empty;
 7629            }
 7630
 07631            var format = state.SupportedSubtitleCodecs.FirstOrDefault();
 7632            string codec;
 7633
 07634            if (string.IsNullOrEmpty(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.Ordin
 7635            {
 07636                codec = "copy";
 7637            }
 7638            else
 7639            {
 07640                codec = format;
 7641            }
 7642
 07643            return " -codec:s:0 " + codec + " -disposition:s:0 default";
 7644        }
 7645
 7646        public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, Encoder
 7647        {
 7648            // Get the output codec name
 07649            var videoCodec = GetVideoEncoder(state, encodingOptions);
 7650
 07651            var format = string.Empty;
 07652            var keyFrame = string.Empty;
 07653            var outputPath = state.OutputFilePath;
 7654
 07655            if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase)
 07656                && state.BaseRequest.Context == EncodingContext.Streaming)
 7657            {
 7658                // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
 07659                format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov";
 7660            }
 7661
 07662            var threads = GetNumberOfThreads(state, encodingOptions, videoCodec);
 7663
 07664            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7665
 07666            return string.Format(
 07667                CultureInfo.InvariantCulture,
 07668                "{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
 07669                inputModifier,
 07670                GetInputArgument(state, encodingOptions, null),
 07671                keyFrame,
 07672                GetMapArgs(state),
 07673                GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset),
 07674                threads,
 07675                GetProgressiveVideoAudioArguments(state, encodingOptions),
 07676                GetSubtitleEmbedArguments(state),
 07677                format,
 07678                outputPath).Trim();
 7679        }
 7680
 7681        public string GetOutputFFlags(EncodingJobInfo state)
 7682        {
 07683            var flags = new List<string>();
 07684            if (state.GenPtsOutput)
 7685            {
 07686                flags.Add("+genpts");
 7687            }
 7688
 07689            if (flags.Count > 0)
 7690            {
 07691                return " -fflags " + string.Join(string.Empty, flags);
 7692            }
 7693
 07694            return string.Empty;
 7695        }
 7696
 7697        public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoC
 7698        {
 07699            var args = "-codec:v:0 " + videoCodec;
 7700
 07701            if (state.BaseRequest.EnableMpegtsM2TsMode)
 7702            {
 07703                args += " -mpegts_m2ts_mode 1";
 7704            }
 7705
 07706            if (IsCopyCodec(videoCodec))
 7707            {
 07708                if (state.VideoStream is not null
 07709                    && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
 07710                    && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
 7711                {
 07712                    string bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Video);
 07713                    if (!string.IsNullOrEmpty(bitStreamArgs))
 7714                    {
 07715                        args += " " + bitStreamArgs;
 7716                    }
 7717                }
 7718
 07719                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7720                {
 07721                    args += " -copyts -avoid_negative_ts disabled -start_at_zero";
 7722                }
 7723
 07724                if (!state.RunTimeTicks.HasValue)
 7725                {
 07726                    args += " -fflags +genpts";
 7727                }
 7728            }
 7729            else
 7730            {
 07731                var keyFrameArg = string.Format(
 07732                    CultureInfo.InvariantCulture,
 07733                    " -force_key_frames \"expr:gte(t,n_forced*{0})\"",
 07734                    5);
 7735
 07736                args += keyFrameArg;
 7737
 07738                var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream &&
 7739
 07740                var hasCopyTs = false;
 7741
 7742                // video processing filters.
 07743                var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
 7744
 07745                var negativeMapArgs = GetNegativeMapArgsByFilters(state, videoProcessParam);
 7746
 07747                args = negativeMapArgs + args + videoProcessParam;
 7748
 07749                hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
 7750
 07751                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7752                {
 07753                    if (!hasCopyTs)
 7754                    {
 07755                        args += " -copyts";
 7756                    }
 7757
 07758                    args += " -avoid_negative_ts disabled";
 7759
 07760                    if (!(state.SubtitleStream is not null && state.SubtitleStream.IsExternal && !state.SubtitleStream.I
 7761                    {
 07762                        args += " -start_at_zero";
 7763                    }
 7764                }
 7765
 07766                var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset);
 7767
 07768                if (!string.IsNullOrEmpty(qualityParam))
 7769                {
 07770                    args += " " + qualityParam.Trim();
 7771                }
 7772            }
 7773
 07774            if (!string.IsNullOrEmpty(state.OutputVideoSync))
 7775            {
 07776                args += GetVideoSyncOption(state.OutputVideoSync, _mediaEncoder.EncoderVersion);
 7777            }
 7778
 07779            args += GetOutputFFlags(state);
 7780
 07781            return args;
 7782        }
 7783
 7784        public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions)
 7785        {
 7786            // If the video doesn't have an audio stream, return a default.
 07787            if (state.AudioStream is null && state.VideoStream is not null)
 7788            {
 07789                return string.Empty;
 7790            }
 7791
 7792            // Get the output codec name
 07793            var codec = GetAudioEncoder(state);
 7794
 07795            var args = "-codec:a:0 " + codec;
 7796
 07797            if (IsCopyCodec(codec))
 7798            {
 07799                return args;
 7800            }
 7801
 07802            var channels = state.OutputAudioChannels;
 7803
 07804            var useDownMixAlgorithm = state.AudioStream is not null
 07805                                      && DownMixAlgorithmsHelper.AlgorithmFilterStrings.ContainsKey((encodingOptions.Dow
 7806
 07807            if (channels.HasValue && !useDownMixAlgorithm)
 7808            {
 07809                args += " -ac " + channels.Value;
 7810            }
 7811
 07812            var bitrate = state.OutputAudioBitrate;
 07813            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
 7814            {
 07815                var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2);
 07816                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7817                {
 07818                    args += vbrParam;
 7819                }
 7820                else
 7821                {
 07822                    args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
 7823                }
 7824            }
 7825
 07826            if (state.OutputAudioSampleRate.HasValue)
 7827            {
 07828                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
 7829            }
 7830
 07831            args += GetAudioFilterParam(state, encodingOptions);
 7832
 07833            return args;
 7834        }
 7835
 7836        public string GetProgressiveAudioFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string 
 7837        {
 07838            var audioTranscodeParams = new List<string>();
 7839
 07840            var bitrate = state.OutputAudioBitrate;
 07841            var channels = state.OutputAudioChannels;
 07842            var outputCodec = state.OutputAudioCodec;
 7843
 07844            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
 7845            {
 07846                var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2);
 07847                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7848                {
 07849                    audioTranscodeParams.Add(vbrParam);
 7850                }
 7851                else
 7852                {
 07853                    audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
 7854                }
 7855            }
 7856
 07857            if (channels.HasValue)
 7858            {
 07859                audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)
 7860            }
 7861
 07862            if (!string.IsNullOrEmpty(outputCodec))
 7863            {
 07864                audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
 7865            }
 7866
 07867            if (GetAudioEncoder(state).StartsWith("pcm_", StringComparison.Ordinal))
 7868            {
 07869                audioTranscodeParams.Add(string.Concat("-f ", GetAudioEncoder(state).AsSpan(4)));
 07870                audioTranscodeParams.Add("-ar " + state.BaseRequest.AudioBitRate);
 7871            }
 7872
 07873            if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
 7874            {
 7875                // opus only supports specific sampling rates
 07876                var sampleRate = state.OutputAudioSampleRate;
 07877                if (sampleRate.HasValue)
 7878                {
 07879                    var sampleRateValue = sampleRate.Value switch
 07880                    {
 07881                        <= 8000 => 8000,
 07882                        <= 12000 => 12000,
 07883                        <= 16000 => 16000,
 07884                        <= 24000 => 24000,
 07885                        _ => 48000
 07886                    };
 7887
 07888                    audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
 7889                }
 7890            }
 7891
 7892            // Copy the movflags from GetProgressiveVideoFullCommandLine
 7893            // See #9248 and the associated PR for why this is needed
 07894            if (_mp4ContainerNames.Contains(state.OutputContainer))
 7895            {
 07896                audioTranscodeParams.Add("-movflags empty_moov+delay_moov");
 7897            }
 7898
 07899            var threads = GetNumberOfThreads(state, encodingOptions, null);
 7900
 07901            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7902
 07903            return string.Format(
 07904                CultureInfo.InvariantCulture,
 07905                "{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"",
 07906                inputModifier,
 07907                GetInputArgument(state, encodingOptions, null),
 07908                threads,
 07909                " -vn",
 07910                string.Join(' ', audioTranscodeParams),
 07911                outputPath,
 07912                string.Empty,
 07913                string.Empty,
 07914                string.Empty).Trim();
 7915        }
 7916
 7917        public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
 7918        {
 167919            var index = 0;
 167920            var length = mediaStreams.Count;
 7921
 567922            for (var i = 0; i < length; i++)
 7923            {
 287924                var currentMediaStream = mediaStreams[i];
 287925                if (currentMediaStream == streamToFind)
 7926                {
 167927                    return index;
 7928                }
 7929
 127930                if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
 7931                {
 127932                    index++;
 7933                }
 7934            }
 7935
 07936            return -1;
 7937        }
 7938
 7939        public static bool IsCopyCodec(string codec)
 7940        {
 297941            return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
 7942        }
 7943
 7944        private static bool ShouldEncodeSubtitle(EncodingJobInfo state)
 7945        {
 107946            return state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
 107947                   || (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec));
 7948        }
 7949
 7950        private static bool NeedsExternalSubtitleMuxing(EncodingJobInfo state)
 7951        {
 47952            return state.SubtitleStream is not null
 47953                && state.SubtitleStream.IsExternal
 47954                && (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed
 47955                    || (ShouldEncodeSubtitle(state) && !state.SubtitleStream.IsTextSubtitleStream));
 7956        }
 7957
 7958        public static string GetVideoSyncOption(string videoSync, Version encoderVersion)
 7959        {
 07960            if (string.IsNullOrEmpty(videoSync))
 7961            {
 07962                return string.Empty;
 7963            }
 7964
 07965            if (encoderVersion >= new Version(5, 1))
 7966            {
 07967                if (int.TryParse(videoSync, CultureInfo.InvariantCulture, out var vsync))
 7968                {
 07969                    return vsync switch
 07970                    {
 07971                        -1 => " -fps_mode auto",
 07972                        0 => " -fps_mode passthrough",
 07973                        1 => " -fps_mode cfr",
 07974                        2 => " -fps_mode vfr",
 07975                        _ => string.Empty
 07976                    };
 7977                }
 7978
 07979                return string.Empty;
 7980            }
 7981
 7982            // -vsync is deprecated in FFmpeg 5.1 and will be removed in the future.
 07983            return $" -vsync {videoSync}";
 7984        }
 7985    }
 7986}

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>)
CanStreamCopyAudio(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Entities.MediaStream,System.Collections.Generic.IEnumerable`1<System.String>,MediaBrowser.Model.Session.TranscodeReason&)
GetAudioStreamCopyFailureReasons(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)