diff options
| author | Nick <20588554+nicknsy@users.noreply.github.com> | 2023-06-28 21:43:23 -0700 |
|---|---|---|
| committer | Nick <20588554+nicknsy@users.noreply.github.com> | 2023-06-28 21:43:23 -0700 |
| commit | 6d9e43cfe03d94aec62a0b35743003fa4caa32b1 (patch) | |
| tree | a78b31f6743061f4c8bfa41985ae6b2574eaa06f /MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | |
| parent | fc619337481e3bb0f686aee06d9c5630926baaef (diff) | |
| parent | 76939bbd9bebb622759422f262c2490b56ab5a16 (diff) | |
Merge branch 'master' into trickplay
Diffstat (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs')
| -rw-r--r-- | MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 211 |
1 files changed, 172 insertions, 39 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 01b6e31e9..0c2a85a02 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -46,6 +46,7 @@ namespace MediaBrowser.Controller.MediaEncoding private readonly Version _minFFmpegImplictHwaccel = new Version(6, 0); private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0); private readonly Version _minFFmpegOclCuTonemapMode = new Version(5, 1, 3); + private readonly Version _minFFmpegSvtAv1Params = new Version(5, 1); private static readonly string[] _videoProfilesH264 = new[] { @@ -65,6 +66,13 @@ namespace MediaBrowser.Controller.MediaEncoding "Main10" }; + private static readonly string[] _videoProfilesAv1 = new[] + { + "Main", + "High", + "Professional", + }; + private static readonly HashSet<string> _mp4ContainerNames = new(StringComparer.OrdinalIgnoreCase) { "mp4", @@ -120,12 +128,15 @@ namespace MediaBrowser.Controller.MediaEncoding } public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) - => GetH264OrH265Encoder("libx264", "h264", state, encodingOptions); + => GetH26xOrAv1Encoder("libx264", "h264", state, encodingOptions); public string GetH265Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) - => GetH264OrH265Encoder("libx265", "hevc", state, encodingOptions); + => GetH26xOrAv1Encoder("libx265", "hevc", state, encodingOptions); + + public string GetAv1Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) + => GetH26xOrAv1Encoder("libsvtav1", "av1", state, encodingOptions); - private string GetH264OrH265Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptions encodingOptions) + private string GetH26xOrAv1Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptions encodingOptions) { // Only use alternative encoders for video files. // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully @@ -234,8 +245,8 @@ namespace MediaBrowser.Controller.MediaEncoding } if (string.Equals(state.VideoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase) - && string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) - && string.Equals(state.VideoStream.VideoRangeType, "DOVI", StringComparison.OrdinalIgnoreCase)) + && state.VideoStream.VideoRange == VideoRange.HDR + && state.VideoStream.VideoRangeType == VideoRangeType.DOVI) { // Only native SW decoder and HW accelerator can parse dovi rpu. var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty; @@ -246,9 +257,9 @@ namespace MediaBrowser.Controller.MediaEncoding return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder; } - return string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) - && (string.Equals(state.VideoStream.VideoRangeType, "HDR10", StringComparison.OrdinalIgnoreCase) - || string.Equals(state.VideoStream.VideoRangeType, "HLG", StringComparison.OrdinalIgnoreCase)); + return state.VideoStream.VideoRange == VideoRange.HDR + && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10 + || state.VideoStream.VideoRangeType == VideoRangeType.HLG); } private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options) @@ -260,7 +271,7 @@ namespace MediaBrowser.Controller.MediaEncoding // libplacebo has partial Dolby Vision to SDR tonemapping support. return options.EnableTonemapping - && string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) + && state.VideoStream.VideoRange == VideoRange.HDR && GetVideoColorBitDepth(state) == 10; } @@ -275,8 +286,8 @@ namespace MediaBrowser.Controller.MediaEncoding // Native VPP tonemapping may come to QSV in the future. - return string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) - && string.Equals(state.VideoStream.VideoRangeType, "HDR10", StringComparison.OrdinalIgnoreCase); + return state.VideoStream.VideoRange == VideoRange.HDR + && state.VideoStream.VideoRangeType == VideoRangeType.HDR10; } /// <summary> @@ -291,6 +302,11 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(codec)) { + if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase)) + { + return GetAv1Encoder(state, encodingOptions); + } + if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)) { @@ -595,6 +611,11 @@ namespace MediaBrowser.Controller.MediaEncoding return Array.FindIndex(_videoProfilesH265, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase)); } + if (string.Equals("av1", videoCodec, StringComparison.OrdinalIgnoreCase)) + { + return Array.FindIndex(_videoProfilesAv1, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase)); + } + return -1; } @@ -1234,6 +1255,11 @@ namespace MediaBrowser.Controller.MediaEncoding return FormattableString.Invariant($" -b:v {bitrate}"); } + if (string.Equals(videoCodec, "libsvtav1", StringComparison.OrdinalIgnoreCase)) + { + return FormattableString.Invariant($" -b:v {bitrate} -bufsize {bufsize}"); + } + if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase)) { @@ -1241,14 +1267,16 @@ namespace MediaBrowser.Controller.MediaEncoding } if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoCodec, "av1_amf", StringComparison.OrdinalIgnoreCase)) { // Override the too high default qmin 18 in transcoding preset return FormattableString.Invariant($" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"); } if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoCodec, "av1_vaapi", StringComparison.OrdinalIgnoreCase)) { // VBR in i965 driver may result in pixelated output. if (_mediaEncoder.IsVaapiDeviceInteli965) @@ -1266,14 +1294,23 @@ namespace MediaBrowser.Controller.MediaEncoding { if (double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel)) { - if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase) - || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase)) + { + // Transcode to level 5.3 (15) and lower for maximum compatibility. + // https://en.wikipedia.org/wiki/AV1#Levels + if (requestLevel < 0 || requestLevel >= 15) + { + return "15"; + } + } + else if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)) { // Transcode to level 5.0 and lower for maximum compatibility. // Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it. // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels // MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880. - if (requestLevel >= 150) + if (requestLevel < 0 || requestLevel >= 150) { return "150"; } @@ -1283,7 +1320,7 @@ namespace MediaBrowser.Controller.MediaEncoding // Transcode to level 5.1 and lower for maximum compatibility. // h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4. // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels - if (requestLevel >= 51) + if (requestLevel < 0 || requestLevel >= 51) { return "51"; } @@ -1421,14 +1458,18 @@ namespace MediaBrowser.Controller.MediaEncoding || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) - || string.Equals(codec, "hevc_amf", StringComparison.OrdinalIgnoreCase)) + || string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "libsvtav1", StringComparison.OrdinalIgnoreCase)) { args += gopArg; } else if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) + || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "av1_vaapi", StringComparison.OrdinalIgnoreCase)) { args += keyFrameArg; @@ -1564,18 +1605,60 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -crf " + defaultCrf; } } + else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)) + { + // Default to use the recommended preset 10. + // Omit presets < 5, which are too slow for on the fly encoding. + // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md + param += encodingOptions.EncoderPreset switch + { + "veryslow" => " -preset 5", + "slower" => " -preset 6", + "slow" => " -preset 7", + "medium" => " -preset 8", + "fast" => " -preset 9", + "faster" => " -preset 10", + "veryfast" => " -preset 11", + "superfast" => " -preset 12", + "ultrafast" => " -preset 13", + _ => " -preset 10" + }; + } + else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase)) + { + // -compression_level is not reliable on AMD. + if (_mediaEncoder.IsVaapiDeviceInteliHD) + { + param += encodingOptions.EncoderPreset switch + { + "veryslow" => " -compression_level 1", + "slower" => " -compression_level 2", + "slow" => " -compression_level 3", + "medium" => " -compression_level 4", + "fast" => " -compression_level 5", + "faster" => " -compression_level 6", + "veryfast" => " -compression_level 7", + "superfast" => " -compression_level 7", + "ultrafast" => " -compression_level 7", + _ => string.Empty + }; + } + } else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv) - || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_qsv) + || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv) + || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv) { - string[] valid_h264_qsv = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" }; + string[] valid_presets = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" }; - if (valid_h264_qsv.Contains(encodingOptions.EncoderPreset, StringComparison.OrdinalIgnoreCase)) + if (valid_presets.Contains(encodingOptions.EncoderPreset, StringComparison.OrdinalIgnoreCase)) { param += " -preset " + encodingOptions.EncoderPreset; } else { - param += " -preset 7"; + param += " -preset veryfast"; } // Only h264_qsv has look_ahead option @@ -1585,7 +1668,8 @@ namespace MediaBrowser.Controller.MediaEncoding } } else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc) - || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc) + || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_nvenc) + || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)) // av1 (av1_nvenc) { switch (encodingOptions.EncoderPreset) { @@ -1625,7 +1709,8 @@ namespace MediaBrowser.Controller.MediaEncoding } } else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf) - || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_amf) + || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf) + || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase)) // av1 (av1_amf) { switch (encodingOptions.EncoderPreset) { @@ -1652,9 +1737,15 @@ namespace MediaBrowser.Controller.MediaEncoding break; } + if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase)) + { + param += " -header_insertion_mode gop"; + } + if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) { - param += " -header_insertion_mode gop -gops_per_idr 1"; + param += " -gops_per_idr 1"; } } else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // vp8 @@ -1785,6 +1876,14 @@ namespace MediaBrowser.Controller.MediaEncoding profile = "high"; } + // We only need Main profile of AV1 encoders. + if (videoEncoder.Contains("av1", StringComparison.OrdinalIgnoreCase) + && (profile.Contains("high", StringComparison.OrdinalIgnoreCase) + || profile.Contains("professional", StringComparison.OrdinalIgnoreCase))) + { + profile = "main"; + } + // h264_vaapi does not support Baseline profile, force Constrained Baseline in this case, // which is compatible (and ugly). if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) @@ -1852,19 +1951,41 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -level " + (hevcLevel / 3); } } + else if (string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)) + { + // libsvtav1 and av1_qsv use -level 60 instead of -level 16 + // https://aomedia.org/av1/specification/annex-a/ + if (int.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out int av1Level)) + { + var x = 2 + (av1Level >> 2); + var y = av1Level & 3; + var res = (x * 10) + y; + param += " -level " + res; + } + } else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase)) { param += " -level " + level; } else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)) { // level option may cause NVENC to fail. // NVENC cannot adjust the given level, just throw an error. + } + else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase)) + { // level option may cause corrupted frames on AMD VAAPI. + if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965) + { + param += " -level " + level; + } } else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) { @@ -1886,6 +2007,12 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -x265-params:0 no-info=1"; } + if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase) + && _mediaEncoder.EncoderVersion >= _minFFmpegSvtAv1Params) + { + param += " -svtav1-params:0 rc=1:tune=0:film-grain=0:enable-overlays=1:enable-tf=0"; + } + return param; } @@ -1964,12 +2091,12 @@ namespace MediaBrowser.Controller.MediaEncoding var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec); if (requestedRangeTypes.Length > 0) { - if (string.IsNullOrEmpty(videoStream.VideoRangeType)) + if (videoStream.VideoRangeType == VideoRangeType.Unknown) { return false; } - if (!requestedRangeTypes.Contains(videoStream.VideoRangeType, StringComparison.OrdinalIgnoreCase)) + if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreCase)) { return false; } @@ -3675,7 +3802,7 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add(swDeintFilter); } - var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p"; + var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12"); var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); // sw scale mainFilters.Add(swScaleFilter); @@ -3876,7 +4003,7 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add(swDeintFilter); } - var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p"; + var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12"); var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); // sw scale mainFilters.Add(swScaleFilter); @@ -5849,19 +5976,25 @@ namespace MediaBrowser.Controller.MediaEncoding private void ShiftVideoCodecsIfNeeded(List<string> videoCodecs, EncodingOptions encodingOptions) { - // Shift hevc/h265 to the end of list if hevc encoding is not allowed. - if (encodingOptions.AllowHevcEncoding) + // No need to shift if there is only one supported video codec. + if (videoCodecs.Count < 2) { return; } - // No need to shift if there is only one supported video codec. - if (videoCodecs.Count < 2) + // Shift codecs to the end of list if it's not allowed. + var shiftVideoCodecs = new List<string>(); + if (!encodingOptions.AllowHevcEncoding) { - return; + shiftVideoCodecs.Add("hevc"); + shiftVideoCodecs.Add("h265"); + } + + if (!encodingOptions.AllowAv1Encoding) + { + shiftVideoCodecs.Add("av1"); } - var shiftVideoCodecs = new[] { "hevc", "h265" }; if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparison.OrdinalIgnoreCase))) { return; |
