aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs')
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs98
1 files changed, 86 insertions, 12 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index a0f4087395..ebfa78176a 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -88,6 +88,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly Version _minFFmpegRkmppHevcDecDoviRpu = new Version(7, 1, 1);
private readonly Version _minFFmpegReadrateCatchupOption = new Version(8, 0);
private readonly Version _minFFmpegNoiseBsfDrop = new Version(5, 0);
+ private readonly Version _minFFmpegAmfAv1EncCAQ = new Version(7, 1);
private static readonly string[] _videoProfilesH264 =
[
@@ -1668,14 +1669,6 @@ namespace MediaBrowser.Controller.MediaEncoding
// TODO: probe QSV encoders' capabilities and enable more tuning options
// See also https://github.com/intel/media-delivery/blob/master/doc/quality.rst
- // Enable MacroBlock level bitrate control for better subjective visual quality
- var mbbrcOpt = string.Empty;
- if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
- {
- mbbrcOpt = " -mbbrc 1";
- }
-
// Some less powerful H.264 HW decoders require strict CPB size
// So bufsize optimizations should not be applied to them
int factor = 2;
@@ -1696,7 +1689,7 @@ namespace MediaBrowser.Controller.MediaEncoding
int qsvInitOcc = (int)Math.Min((long)bitrate * 1 * factor, int.MaxValue);
int qsvBufsize = (int)Math.Min((long)bitrate * 2 * factor, int.MaxValue);
- return FormattableString.Invariant($"{mbbrcOpt} -b:v {bitrate} -maxrate {qsvMaxrate} -rc_init_occupancy {qsvInitOcc} -bufsize {qsvBufsize}");
+ return string.Create(CultureInfo.InvariantCulture, $" -b:v {bitrate} -maxrate {qsvMaxrate} -rc_init_occupancy {qsvInitOcc} -bufsize {qsvBufsize}");
}
if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
@@ -1813,6 +1806,13 @@ namespace MediaBrowser.Controller.MediaEncoding
EncoderPreset[] valid_presets = [EncoderPreset.veryslow, EncoderPreset.slower, EncoderPreset.slow, EncoderPreset.medium, EncoderPreset.fast, EncoderPreset.faster, EncoderPreset.veryfast];
param += " -preset " + (valid_presets.Contains(encoderPreset) ? encoderPreset : EncoderPreset.veryfast).ToString().ToLowerInvariant();
+
+ // Enable MacroBlock level bitrate control for better subjective visual quality
+ if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ param += " -mbbrc 1";
+ }
}
else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
|| string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_nvenc)
@@ -1829,6 +1829,13 @@ namespace MediaBrowser.Controller.MediaEncoding
EncoderPreset.faster => " -preset p2",
_ => " -preset p1"
};
+
+ // Enable Spatial Adaptive Quantization for better subjective visual quality
+ // H.264 is skipped for now because Maxwell 1 does not support it
+ if (!string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ param += " -spatial-aq 1 -aq-strength 15";
+ }
}
else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf)
|| string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf)
@@ -1844,6 +1851,18 @@ namespace MediaBrowser.Controller.MediaEncoding
_ => " -quality speed"
};
+ // Enable Variance Based/Context Adaptive Quantization for better subjective visual quality
+ if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
+ {
+ param += " -vbaq 1";
+ }
+ else if (string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase)
+ && _mediaEncoder.EncoderVersion >= _minFFmpegAmfAv1EncCAQ)
+ {
+ param += " -aq_mode caq";
+ }
+
if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
{
@@ -2693,6 +2712,28 @@ namespace MediaBrowser.Controller.MediaEncoding
return reasons;
}
+ private static int GetBitDepthCompensatedBitrate(int bitrate, MediaStream videoStream, int targetBitDepth)
+ {
+ if (targetBitDepth >= 10
+ || (videoStream.BitDepth is int bitDepth && bitDepth <= targetBitDepth)
+ || videoStream.BitRate is not int sourceBitrate)
+ {
+ return bitrate;
+ }
+
+ // Bit depth reduction requires bit rate compensation because
+ // 10-bit encoded video is significantly more efficient.
+ var factor = sourceBitrate switch
+ {
+ <= 4_000_000 => 1.5,
+ <= 8_000_000 => 1.2,
+ <= 12_000_000 => 1.1,
+ _ => 1.05
+ };
+
+ return Convert.ToInt32(bitrate * factor);
+ }
+
public int GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideoCodec)
{
var bitrate = request.VideoBitRate;
@@ -2715,6 +2756,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (bitrate.HasValue)
{
var inputVideoCodec = videoStream.Codec;
+ bitrate = GetBitDepthCompensatedBitrate(bitrate.Value, videoStream, 8);
bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
// If a max bitrate was requested, don't let the scaled bitrate exceed it
@@ -3807,6 +3849,11 @@ namespace MediaBrowser.Controller.MediaEncoding
var formatArg = isFormatFixed ? (":format=" + videoFormat) : string.Empty;
var tonemapArg = string.Empty;
+ // libplacebo only support full range RGB
+ forceFullRange = forceFullRange
+ || (videoFormat ?? string.Empty).Contains("rgb", StringComparison.OrdinalIgnoreCase)
+ || (videoFormat ?? string.Empty).Contains("bgr", StringComparison.OrdinalIgnoreCase);
+
if (doTonemap)
{
var algorithm = options.TonemappingAlgorithm;
@@ -3830,6 +3877,10 @@ namespace MediaBrowser.Controller.MediaEncoding
tonemapArg += ":range=" + range.ToString().ToLowerInvariant();
}
}
+ else if (forceFullRange)
+ {
+ formatArg += ":range=pc";
+ }
return string.Format(
CultureInfo.InvariantCulture,
@@ -5470,7 +5521,14 @@ namespace MediaBrowser.Controller.MediaEncoding
mainFilters.Add("format=vaapi");
// clear the surf->meta_offset and output nv12
- mainFilters.Add("scale_vaapi=format=nv12");
+ var hwCscFilter = "scale_vaapi=format=nv12";
+
+ if (!isMjpegEncoder && options.TonemappingRange != TonemappingRange.pc)
+ {
+ hwCscFilter += ":out_range=tv";
+ }
+
+ mainFilters.Add(hwCscFilter);
// hw deint
if (doDeintH2645)
@@ -5540,7 +5598,14 @@ namespace MediaBrowser.Controller.MediaEncoding
overlayFilters.Add("format=vaapi");
// clear the surf->meta_offset and output nv12
- overlayFilters.Add("scale_vaapi=format=nv12");
+ var hwCscFilter = "scale_vaapi=format=nv12";
+
+ if (!doVkTonemap || (doVkTonemap && options.TonemappingRange != TonemappingRange.pc))
+ {
+ hwCscFilter += ":out_range=tv";
+ }
+
+ overlayFilters.Add(hwCscFilter);
// hw deint
if (doDeintH2645)
@@ -7809,7 +7874,9 @@ namespace MediaBrowser.Controller.MediaEncoding
var channels = state.OutputAudioChannels;
- var useDownMixAlgorithm = state.AudioStream is not null
+ // Must match the condition under which GetAudioFilterParam emits the downmix filter.
+ var useDownMixAlgorithm = channels == 2
+ && state.AudioStream?.Channels > 2
&& DownMixAlgorithmsHelper.AlgorithmFilterStrings.ContainsKey((encodingOptions.DownMixStereoAlgorithm, DownMixAlgorithmsHelper.InferChannelLayout(state.AudioStream)));
if (channels.HasValue && !useDownMixAlgorithm)
@@ -7904,6 +7971,13 @@ namespace MediaBrowser.Controller.MediaEncoding
audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
}
+ // Without the downmix filter, -ac 2 alone drops the LFE channel.
+ var audioFilterParam = GetAudioFilterParam(state, encodingOptions);
+ if (!string.IsNullOrEmpty(audioFilterParam))
+ {
+ audioTranscodeParams.Add(audioFilterParam.TrimStart());
+ }
+
// Copy the movflags from GetProgressiveVideoFullCommandLine
// See #9248 and the associated PR for why this is needed
if (_mp4ContainerNames.Contains(state.OutputContainer))