aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding
diff options
context:
space:
mode:
authorCody Robibero <cody@robibe.ro>2021-12-26 17:32:06 +0000
committerGitHub <noreply@github.com>2021-12-26 17:32:06 +0000
commit78bb581f0c7d27f7f9a7a6d3ac81a2a6c6fbe781 (patch)
treef5274d10f55f4c103f11642e965925ffbf8648e3 /MediaBrowser.MediaEncoding
parenta615f87680eeb847807a47168bb4c5551a48e59d (diff)
parent728a5988b3801dc559efbaf278e1a75770884faf (diff)
Merge pull request #6934 from nyanmisaka/hwa
HWA pipeline refactor, AMD/Intel/Nvidia full hardware filtering support, AV1 hwdec
Diffstat (limited to 'MediaBrowser.MediaEncoding')
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs80
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs136
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs27
3 files changed, 144 insertions, 99 deletions
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 60a2d39e5..fe3069934 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -16,6 +16,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
"h264",
"hevc",
+ "vp8",
+ "libvpx",
+ "vp9",
+ "libvpx-vp9",
+ "av1",
+ "libdav1d",
"mpeg2video",
"mpeg4",
"msmpeg4",
@@ -30,6 +36,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
"vc1_qsv",
"vp8_qsv",
"vp9_qsv",
+ "av1_qsv",
"h264_cuvid",
"hevc_cuvid",
"mpeg2_cuvid",
@@ -37,16 +44,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
"mpeg4_cuvid",
"vp8_cuvid",
"vp9_cuvid",
+ "av1_cuvid",
"h264_mmal",
"mpeg2_mmal",
"mpeg4_mmal",
"vc1_mmal",
- "h264_mediacodec",
- "hevc_mediacodec",
- "mpeg2_mediacodec",
- "mpeg4_mediacodec",
- "vp8_mediacodec",
- "vp9_mediacodec",
"h264_opencl",
"hevc_opencl",
"mpeg2_opencl",
@@ -89,20 +91,39 @@ namespace MediaBrowser.MediaEncoding.Encoder
private static readonly string[] _requiredFilters = new[]
{
+ // sw
+ "alphasrc",
+ "zscale",
+ // qsv
+ "scale_qsv",
+ "vpp_qsv",
+ "deinterlace_qsv",
+ "overlay_qsv",
+ // cuda
"scale_cuda",
"yadif_cuda",
- "hwupload_cuda",
- "overlay_cuda",
"tonemap_cuda",
+ "overlay_cuda",
+ "hwupload_cuda",
+ // opencl
+ "scale_opencl",
"tonemap_opencl",
+ "overlay_opencl",
+ // vaapi
+ "scale_vaapi",
+ "deinterlace_vaapi",
"tonemap_vaapi",
+ "overlay_vaapi",
+ "hwupload_vaapi"
};
private static readonly IReadOnlyDictionary<int, string[]> _filterOptionsDict = new Dictionary<int, string[]>
{
{ 0, new string[] { "scale_cuda", "Output format (default \"same\")" } },
{ 1, new string[] { "tonemap_cuda", "GPU accelerated HDR to SDR tonemapping" } },
- { 2, new string[] { "tonemap_opencl", "bt2390" } }
+ { 2, new string[] { "tonemap_opencl", "bt2390" } },
+ { 3, new string[] { "overlay_opencl", "Action to take when encountering EOF from secondary input" } },
+ { 4, new string[] { "overlay_vaapi", "Action to take when encountering EOF from secondary input" } }
};
// These are the library versions that corresponds to our minimum ffmpeg version 4.x according to the version table below
@@ -144,7 +165,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output;
try
{
- output = GetProcessOutput(_encoderPath, "-version");
+ output = GetProcessOutput(_encoderPath, "-version", false);
}
catch (Exception ex)
{
@@ -225,7 +246,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output;
try
{
- output = GetProcessOutput(_encoderPath, "-version");
+ output = GetProcessOutput(_encoderPath, "-version", false);
}
catch (Exception ex)
{
@@ -318,12 +339,36 @@ namespace MediaBrowser.MediaEncoding.Encoder
return map;
}
+ public bool CheckVaapiDeviceByDriverName(string driverName, string renderNodePath)
+ {
+ if (!OperatingSystem.IsLinux())
+ {
+ return false;
+ }
+
+ if (string.IsNullOrEmpty(driverName) || string.IsNullOrEmpty(renderNodePath))
+ {
+ return false;
+ }
+
+ try
+ {
+ var output = GetProcessOutput(_encoderPath, "-v verbose -hide_banner -init_hw_device vaapi=va:" + renderNodePath, true);
+ return output.Contains(driverName, StringComparison.Ordinal);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error detecting the given vaapi render node path");
+ return false;
+ }
+ }
+
private IEnumerable<string> GetHwaccelTypes()
{
string? output = null;
try
{
- output = GetProcessOutput(_encoderPath, "-hwaccels");
+ output = GetProcessOutput(_encoderPath, "-hwaccels", false);
}
catch (Exception ex)
{
@@ -351,7 +396,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output;
try
{
- output = GetProcessOutput(_encoderPath, "-h filter=" + filter);
+ output = GetProcessOutput(_encoderPath, "-h filter=" + filter, false);
}
catch (Exception ex)
{
@@ -375,7 +420,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output;
try
{
- output = GetProcessOutput(_encoderPath, "-" + codecstr);
+ output = GetProcessOutput(_encoderPath, "-" + codecstr, false);
}
catch (Exception ex)
{
@@ -406,7 +451,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output;
try
{
- output = GetProcessOutput(_encoderPath, "-filters");
+ output = GetProcessOutput(_encoderPath, "-filters", false);
}
catch (Exception ex)
{
@@ -444,7 +489,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
return dict;
}
- private string GetProcessOutput(string path, string arguments)
+ private string GetProcessOutput(string path, string arguments, bool readStdErr)
{
using (var process = new Process()
{
@@ -455,7 +500,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false,
RedirectStandardOutput = true,
- // ffmpeg uses stderr to log info, don't show this
RedirectStandardError = true
}
})
@@ -464,7 +508,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
process.Start();
- return process.StandardOutput.ReadToEnd();
+ return readStdErr ? process.StandardError.ReadToEnd() : process.StandardOutput.ReadToEnd();
}
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 1c97a1982..fce71bf1a 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -65,6 +65,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
private List<string> _filters = new List<string>();
private IDictionary<int, bool> _filtersWithOption = new Dictionary<int, bool>();
+ private bool _isVaapiDeviceAmd = false;
+ private bool _isVaapiDeviceInteliHD = false;
+ private bool _isVaapiDeviceInteli965 = false;
+
private Version _ffmpegVersion = null;
private string _ffmpegPath = string.Empty;
private string _ffprobePath;
@@ -87,6 +91,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <inheritdoc />
public string EncoderPath => _ffmpegPath;
+ public Version EncoderVersion => _ffmpegVersion;
+ public bool IsVaapiDeviceAmd => _isVaapiDeviceAmd;
+ public bool IsVaapiDeviceInteliHD => _isVaapiDeviceInteliHD;
+ public bool IsVaapiDeviceInteli965 => _isVaapiDeviceInteli965;
/// <summary>
/// Run at startup or if the user removes a Custom path from transcode page.
@@ -114,9 +122,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
// Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
- var config = _configurationManager.GetEncodingOptions();
- config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
- _configurationManager.SaveConfiguration("encoding", config);
+ var options = _configurationManager.GetEncodingOptions();
+ options.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
+ _configurationManager.SaveConfiguration("encoding", options);
// Only if mpeg path is set, try and set path to probe
if (_ffmpegPath != null)
@@ -134,7 +142,30 @@ namespace MediaBrowser.MediaEncoding.Encoder
SetAvailableHwaccels(validator.GetHwaccels());
SetMediaEncoderVersion(validator);
- _threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null);
+ _threads = EncodingHelper.GetNumberOfThreads(null, options, null);
+
+ // Check the Vaapi device vendor
+ if (OperatingSystem.IsLinux()
+ && SupportsHwaccel("vaapi")
+ && !string.IsNullOrEmpty(options.VaapiDevice)
+ && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ _isVaapiDeviceAmd = validator.CheckVaapiDeviceByDriverName("Mesa Gallium driver", options.VaapiDevice);
+ _isVaapiDeviceInteliHD = validator.CheckVaapiDeviceByDriverName("Intel iHD driver", options.VaapiDevice);
+ _isVaapiDeviceInteli965 = validator.CheckVaapiDeviceByDriverName("Intel i965 driver", options.VaapiDevice);
+ if (_isVaapiDeviceAmd)
+ {
+ _logger.LogInformation("VAAPI device {RenderNodePath} is AMD GPU", options.VaapiDevice);
+ }
+ else if (_isVaapiDeviceInteliHD)
+ {
+ _logger.LogInformation("VAAPI device {RenderNodePath} is Intel GPU (iHD)", options.VaapiDevice);
+ }
+ else if (_isVaapiDeviceInteli965)
+ {
+ _logger.LogInformation("VAAPI device {RenderNodePath} is Intel GPU (i965)", options.VaapiDevice);
+ }
+ }
}
_logger.LogInformation("FFmpeg: {FfmpegPath}", _ffmpegPath ?? string.Empty);
@@ -301,11 +332,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
return false;
}
- public Version GetMediaEncoderVersion()
- {
- return _ffmpegVersion;
- }
-
public bool CanEncodeToAudioCodec(string codec)
{
if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
@@ -508,36 +534,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!isAudio)
{
- // The failure of HDR extraction usually occurs when using custom ffmpeg that does not contain the zscale filter.
try
{
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, targetFormat, cancellationToken).ConfigureAwait(false);
- }
- catch (ArgumentException)
- {
- throw;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "I-frame or HDR image extraction failed, will attempt with I-frame extraction disabled. Input: {Arguments}", inputArgument);
- }
-
- try
- {
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, targetFormat, cancellationToken).ConfigureAwait(false);
- }
- catch (ArgumentException)
- {
- throw;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "HDR image extraction failed, will fallback to SDR image extraction. Input: {Arguments}", inputArgument);
- }
-
- try
- {
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, targetFormat, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, targetFormat, cancellationToken).ConfigureAwait(false);
}
catch (ArgumentException)
{
@@ -549,10 +548,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, targetFormat, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, targetFormat, cancellationToken).ConfigureAwait(false);
}
- private async Task<string> ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, ImageFormat? targetFormat, CancellationToken cancellationToken)
+ private async Task<string> ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, ImageFormat? targetFormat, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(inputPath))
{
@@ -572,36 +571,32 @@ namespace MediaBrowser.MediaEncoding.Encoder
var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + outputExtension);
Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath));
+ // deint -> scale -> thumbnail -> tonemap.
+ // put the SW tonemap right after the thumbnail to do it only once to reduce cpu usage.
+ var filters = new List<string>();
+
+ // deinterlace using bwdif algorithm for video stream.
+ if (videoStream != null && videoStream.IsInterlaced)
+ {
+ filters.Add("bwdif=0:-1:0");
+ }
+
// apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar.
// This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar
- var vf = threedFormat switch
+ var scaler = threedFormat switch
{
// hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not.
- Video3DFormat.HalfSideBySide => "-vf crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
+ Video3DFormat.HalfSideBySide => "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
// fsbs crop width in half,set the display aspect,crop out any black bars we may have made
- Video3DFormat.FullSideBySide => "-vf crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
+ Video3DFormat.FullSideBySide => "crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
// htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made
- Video3DFormat.HalfTopAndBottom => "-vf crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
+ Video3DFormat.HalfTopAndBottom => "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
// ftab crop heigt in half, set the display aspect,crop out any black bars we may have made
- Video3DFormat.FullTopAndBottom => "-vf crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
- _ => string.Empty
+ Video3DFormat.FullTopAndBottom => "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
+ _ => "scale=trunc(iw*sar):ih"
};
- var mapArg = imageStreamIndex.HasValue ? (" -map 0:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
-
- var enableHdrExtraction = allowTonemap && string.Equals(videoStream?.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase);
- if (enableHdrExtraction)
- {
- string tonemapFilters = "zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709,format=yuv420p";
- if (vf.Length == 0)
- {
- vf = "-vf " + tonemapFilters;
- }
- else
- {
- vf += "," + tonemapFilters;
- }
- }
+ filters.Add(scaler);
// Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
// mpegts need larger batch size otherwise the corrupted thumbnail will be created. Larger batch size will lower the processing speed.
@@ -609,18 +604,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (enableThumbnail)
{
var useLargerBatchSize = string.Equals("mpegts", container, StringComparison.OrdinalIgnoreCase);
- var batchSize = useLargerBatchSize ? "50" : "24";
- if (string.IsNullOrEmpty(vf))
- {
- vf = "-vf thumbnail=" + batchSize;
- }
- else
- {
- vf += ",thumbnail=" + batchSize;
- }
+ filters.Add("thumbnail=n=" + (useLargerBatchSize ? "50" : "24"));
}
- var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads);
+ // Use SW tonemap on HDR video stream only when the zscale filter is available.
+ var enableHdrExtraction = string.Equals(videoStream?.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) && SupportsFilter("zscale");
+ if (enableHdrExtraction)
+ {
+ filters.Add("zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709,format=yuv420p");
+ }
+
+ var vf = string.Join(',', filters);
+ var mapArg = imageStreamIndex.HasValue ? (" -map 0:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
+ var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 -vf {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads);
if (offset.HasValue)
{
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 4e4957ef7..750fd44eb 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -777,18 +777,23 @@ namespace MediaBrowser.MediaEncoding.Probing
if (!stream.BitDepth.HasValue)
{
- if (!string.IsNullOrEmpty(streamInfo.PixelFormat)
- && streamInfo.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrEmpty(streamInfo.PixelFormat))
{
- stream.BitDepth = 10;
- }
-
- if (!string.IsNullOrEmpty(streamInfo.Profile)
- && (streamInfo.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
- || streamInfo.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase)
- || streamInfo.Profile.Contains("Profile 2", StringComparison.OrdinalIgnoreCase)))
- {
- stream.BitDepth = 10;
+ if (string.Equals(streamInfo.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(streamInfo.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
+ {
+ stream.BitDepth = 8;
+ }
+ else if (string.Equals(streamInfo.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(streamInfo.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
+ {
+ stream.BitDepth = 10;
+ }
+ else if (string.Equals(streamInfo.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(streamInfo.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
+ {
+ stream.BitDepth = 12;
+ }
}
}