diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 9b5edabc06..14547d4406 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -559,6 +559,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
{
+ // Use Apple's aac encoder if available as it provides best audio quality
+ if (_mediaEncoder.SupportsEncoder("aac_at"))
+ {
+ return "aac_at";
+ }
+
// Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
if (_mediaEncoder.SupportsEncoder("libfdk_aac"))
{
@@ -2814,6 +2820,13 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return "deinterlace_qsv=mode=2";
}
+ else if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
+ {
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "yadif_videotoolbox={0}:-1:0",
+ doubleRateDeint ? "1" : "0");
+ }
return string.Empty;
}
@@ -4450,6 +4463,75 @@ namespace MediaBrowser.Controller.MediaEncoding
return (mainFilters, subFilters, overlayFilters);
}
+ ///
+ /// Gets the parameter of Apple VideoToolBox filter chain.
+ ///
+ /// Encoding state.
+ /// Encoding options.
+ /// Video encoder to use.
+ /// The tuple contains three lists: main, sub and overlay filters.
+ public (List MainFilters, List SubFilters, List OverlayFilters) GetAppleVidFilterChain(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string vidEncoder)
+ {
+ if (!string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
+ {
+ return (null, null, null);
+ }
+
+ var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
+
+ if (!options.EnableHardwareEncoding)
+ {
+ return swFilterChain;
+ }
+
+ if (_mediaEncoder.EncoderVersion.CompareTo(new Version("5.0.0")) < 0)
+ {
+ // All features used here requires ffmpeg 5.0 or later, fallback to software filters if using an old ffmpeg
+ return swFilterChain;
+ }
+
+ var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
+ var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
+ var doDeintH2645 = doDeintH264 || doDeintHevc;
+ var inW = state.VideoStream?.Width;
+ var inH = state.VideoStream?.Height;
+ var reqW = state.BaseRequest.Width;
+ var reqH = state.BaseRequest.Height;
+ var reqMaxW = state.BaseRequest.MaxWidth;
+ var reqMaxH = state.BaseRequest.MaxHeight;
+ var threeDFormat = state.MediaSource.Video3DFormat;
+ var newfilters = new List();
+ var noOverlay = swFilterChain.OverlayFilters.Count == 0;
+ var supportsHwDeint = _mediaEncoder.SupportsFilter("yadif_videotoolbox");
+ // fallback to software filters if we are using filters not supported by hardware yet.
+ var useHardwareFilters = noOverlay && (!doDeintH2645 || supportsHwDeint);
+
+ if (!useHardwareFilters)
+ {
+ return swFilterChain;
+ }
+
+ // ffmpeg cannot use videotoolbox to scale
+ var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ newfilters.Add(swScaleFilter);
+
+ // hwupload on videotoolbox encoders can automatically convert AVFrame into its CVPixelBuffer equivalent
+ // videotoolbox will automatically convert the CVPixelBuffer to a pixel format the encoder supports, so we don't have to set a pixel format explicitly here
+ // This will reduce CPU usage significantly on UHD videos with 10 bit colors because we bypassed the ffmpeg pixel format conversion
+ newfilters.Add("hwupload");
+
+ if (doDeintH2645)
+ {
+ var deintFilter = GetHwDeinterlaceFilter(state, options, "videotoolbox");
+ newfilters.Add(deintFilter);
+ }
+
+ return (newfilters, swFilterChain.SubFilters, swFilterChain.OverlayFilters);
+ }
+
///
/// Gets the parameter of video processing filters.
///
@@ -4492,6 +4574,10 @@ namespace MediaBrowser.Controller.MediaEncoding
{
(mainFilters, subFilters, overlayFilters) = GetAmdVidFilterChain(state, options, outputVideoCodec);
}
+ else if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
+ {
+ (mainFilters, subFilters, overlayFilters) = GetAppleVidFilterChain(state, options, outputVideoCodec);
+ }
else
{
(mainFilters, subFilters, overlayFilters) = GetSwVidFilterChain(state, options, outputVideoCodec);
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 8479b7d50d..9e6134b524 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -56,6 +56,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
"libvpx",
"libvpx-vp9",
"aac",
+ "aac_at",
"libfdk_aac",
"ac3",
"libmp3lame",
@@ -106,7 +107,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
// vulkan
"libplacebo",
"scale_vulkan",
- "overlay_vulkan"
+ "overlay_vulkan",
+ "hwupload_vaapi",
+ // videotoolbox
+ "yadif_videotoolbox"
};
private static readonly IReadOnlyDictionary _filterOptionsDict = new Dictionary