diff options
| author | Matt Montgomery <33811686+ConfusedPolarBear@users.noreply.github.com> | 2020-07-26 16:14:58 -0500 |
|---|---|---|
| committer | Matt Montgomery <33811686+ConfusedPolarBear@users.noreply.github.com> | 2020-07-26 16:14:58 -0500 |
| commit | a40fe867762ee8b538848b164952d951c3534485 (patch) | |
| tree | 747a24b766e9a66339edaa45433103095e735814 /MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | |
| parent | e2f16fc2551592541846c2bd34f27773f33aae7e (diff) | |
| parent | 8ab800508bb140d8671125245a64c9d27adcad13 (diff) | |
Merge remote-tracking branch 'upstream/master' into quickconnect
Diffstat (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs')
| -rw-r--r-- | MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 415 |
1 files changed, 265 insertions, 150 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 8da714858..c20c9c2c4 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using Jellyfin.Data.Enums; @@ -371,7 +372,7 @@ namespace MediaBrowser.Controller.MediaEncoding public int GetVideoProfileScore(string profile) { // strip spaces because they may be stripped out on the query string - profile = profile.Replace(" ", ""); + profile = profile.Replace(" ", string.Empty, StringComparison.Ordinal); return Array.FindIndex(_videoProfiles, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase)); } @@ -449,41 +450,59 @@ namespace MediaBrowser.Controller.MediaEncoding var arg = new StringBuilder(); var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty; var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty; - bool isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; - bool isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; - bool isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; - bool isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; + var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; + var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; + var isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; + var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; + var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - if (state.IsVideoRequest - && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + if (!IsCopyCodec(outputVideoCodec)) { - if (isVaapiDecoder) + if (state.IsVideoRequest + && _mediaEncoder.SupportsHwaccel("vaapi") + && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { - arg.Append("-hwaccel_output_format vaapi ") - .Append("-vaapi_device ") - .Append(encodingOptions.VaapiDevice) - .Append(" "); - } - else if (!isVaapiDecoder && isVaapiEncoder) - { - arg.Append("-vaapi_device ") - .Append(encodingOptions.VaapiDevice) - .Append(" "); + if (isVaapiDecoder) + { + arg.Append("-hwaccel_output_format vaapi ") + .Append("-vaapi_device ") + .Append(encodingOptions.VaapiDevice) + .Append(' '); + } + else if (!isVaapiDecoder && isVaapiEncoder) + { + arg.Append("-vaapi_device ") + .Append(encodingOptions.VaapiDevice) + .Append(' '); + } } - } - if (state.IsVideoRequest - && string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) - { - var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - - if (!hasTextSubs) + if (state.IsVideoRequest + && string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { - if (isQsvEncoder) + var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + + if (isQsvEncoder) { if (isQsvDecoder) { - arg.Append("-hwaccel qsv "); + if (isLinux) + { + if (hasGraphicalSubs) + { + arg.Append("-init_hw_device qsv=hw -filter_hw_device hw "); + } + else + { + arg.Append("-hwaccel qsv "); + } + } + + if (isWindows) + { + arg.Append("-hwaccel qsv "); + } } // While using SW decoder else @@ -806,6 +825,34 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } + else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) + { + switch (encodingOptions.EncoderPreset) + { + case "veryslow": + case "slow": + case "slower": + param += "-quality quality"; + break; + + case "medium": + param += "-quality balanced"; + break; + + case "fast": + case "faster": + case "veryfast": + case "superfast": + case "ultrafast": + param += "-quality speed"; + break; + + default: + param += "-quality speed"; + break; + } + } else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // webm { // Values 0-3, 0 being highest quality but slower @@ -1351,7 +1398,7 @@ namespace MediaBrowser.Controller.MediaEncoding transcoderChannelLimit = 6; } - var isTranscodingAudio = !EncodingHelper.IsCopyCodec(codec); + var isTranscodingAudio = !IsCopyCodec(codec); int? resultChannels = state.GetRequestedAudioChannels(codec); if (isTranscodingAudio) @@ -1541,7 +1588,7 @@ namespace MediaBrowser.Controller.MediaEncoding { outputVideoCodec ??= string.Empty; - var outputSizeParam = string.Empty; + var outputSizeParam = ReadOnlySpan<char>.Empty; var request = state.BaseRequest; // Add resolution params, if specified @@ -1555,28 +1602,44 @@ namespace MediaBrowser.Controller.MediaEncoding var index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase); if (index != -1) { - outputSizeParam = "," + outputSizeParam.Substring(index); + outputSizeParam = outputSizeParam.Slice(index); } else { - index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase); + index = outputSizeParam.IndexOf("hwupload=extra_hw_frames", StringComparison.OrdinalIgnoreCase); if (index != -1) { - outputSizeParam = "," + outputSizeParam.Substring(index); + outputSizeParam = outputSizeParam.Slice(index); } else { - index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase); + index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase); if (index != -1) { - outputSizeParam = "," + outputSizeParam.Substring(index); + outputSizeParam = outputSizeParam.Slice(index); } else { - index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase); + index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase); if (index != -1) { - outputSizeParam = "," + outputSizeParam.Substring(index); + outputSizeParam = outputSizeParam.Slice(index); + } + else + { + index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase); + if (index != -1) + { + outputSizeParam = outputSizeParam.Slice(index); + } + else + { + index = outputSizeParam.IndexOf("vpp", StringComparison.OrdinalIgnoreCase); + if (index != -1) + { + outputSizeParam = outputSizeParam.Slice(index); + } + } } } } @@ -1585,43 +1648,30 @@ namespace MediaBrowser.Controller.MediaEncoding var videoSizeParam = string.Empty; var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty; + var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); // Setup subtitle scaling if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue) { - videoSizeParam = string.Format( - CultureInfo.InvariantCulture, - "scale={0}:{1}", - state.VideoStream.Width.Value, - state.VideoStream.Height.Value); + // Adjust the size of graphical subtitles to fit the video stream. + var videoStream = state.VideoStream; + var inputWidth = videoStream?.Width; + var inputHeight = videoStream?.Height; + var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); - // For QSV, feed it into hardware encoder now - if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) - { - videoSizeParam += ",hwupload=extra_hw_frames=64"; - } - - // For VAAPI and CUVID decoder - // these encoders cannot automatically adjust the size of graphical subtitles to fit the output video, - // thus needs to be manually adjusted. - if (videoDecoder.IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1 - || (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) - && (videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 - || outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1))) + if (width.HasValue && height.HasValue) { - var videoStream = state.VideoStream; - var inputWidth = videoStream?.Width; - var inputHeight = videoStream?.Height; - var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); - - if (width.HasValue && height.HasValue) - { - videoSizeParam = string.Format( + videoSizeParam = string.Format( CultureInfo.InvariantCulture, - "scale={0}:{1}", + "scale={0}x{1}", width.Value, height.Value); - } + } + + // For QSV, feed it into hardware encoder now + if (isLinux && string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + { + videoSizeParam += ",hwupload=extra_hw_frames=64"; } } @@ -1634,7 +1684,10 @@ namespace MediaBrowser.Controller.MediaEncoding : state.SubtitleStream.Index; // Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference) - var retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay{3}\""; + // Always put the scaler before the overlay for better performance + var retStr = !outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\""; // When the input may or may not be hardware VAAPI decodable if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) @@ -1644,12 +1697,11 @@ namespace MediaBrowser.Controller.MediaEncoding [sub]: SW scaling subtitle to FixedOutputSize [base][sub]: SW overlay */ - outputSizeParam = outputSizeParam.TrimStart(','); retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""; } // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first - else if (IsVaapiSupported(state) && videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 + else if (_mediaEncoder.SupportsHwaccel("vaapi") && videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) { /* @@ -1657,7 +1709,6 @@ namespace MediaBrowser.Controller.MediaEncoding [sub]: SW scaling subtitle to FixedOutputSize [base][sub]: SW overlay */ - outputSizeParam = outputSizeParam.TrimStart(','); retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""; } else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) @@ -1666,14 +1717,13 @@ namespace MediaBrowser.Controller.MediaEncoding QSV in FFMpeg can now setup hardware overlay for transcodes. For software decoding and hardware encoding option, frames must be hwuploaded into hardware with fixed frame size. + Currently only supports linux. */ - if (videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1) - { - retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv=x=(W-w)/2:y=(H-h)/2{3}\""; - } - else + if (isLinux) { - retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwupload=extra_hw_frames=64[v];[v][sub]overlay_qsv=x=(W-w)/2:y=(H-h)/2{3}\""; + retStr = !outputSizeParam.IsEmpty ? + " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"" : + " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\""; } } @@ -1683,7 +1733,7 @@ namespace MediaBrowser.Controller.MediaEncoding mapPrefix, subtitleStreamIndex, state.VideoStream.Index, - outputSizeParam, + outputSizeParam.ToString(), videoSizeParam); } @@ -1745,10 +1795,8 @@ namespace MediaBrowser.Controller.MediaEncoding requestedMaxWidth, requestedMaxHeight); - var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - || (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && !hasTextSubs)) + || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)) && width.HasValue && height.HasValue) { @@ -1757,7 +1805,11 @@ namespace MediaBrowser.Controller.MediaEncoding // output dimensions. Output dimensions are guaranteed to be even. var outputWidth = width.Value; var outputHeight = height.Value; - var vaapi_or_qsv = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) ? "qsv" : "vaapi"; + var qsv_or_vaapi = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase); + var isDeintEnabled = state.DeInterlace("h264", true) + || state.DeInterlace("avc", true) + || state.DeInterlace("h265", true) + || state.DeInterlace("hevc", true); if (!videoWidth.HasValue || outputWidth != videoWidth.Value @@ -1765,17 +1817,24 @@ namespace MediaBrowser.Controller.MediaEncoding || outputHeight != videoHeight.Value) { // Force nv12 pixel format to enable 10-bit to 8-bit colour conversion. + // use vpp_qsv filter to avoid green bar when the fixed output size is requested. filters.Add( string.Format( CultureInfo.InvariantCulture, - "scale_{0}=w={1}:h={2}:format=nv12", - vaapi_or_qsv, + "{0}=w={1}:h={2}:format=nv12{3}", + qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi", outputWidth, - outputHeight)); + outputHeight, + (qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty)); } else { - filters.Add(string.Format(CultureInfo.InvariantCulture, "scale_{0}=format=nv12", vaapi_or_qsv)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "{0}=format=nv12{1}", + qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi", + (qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty)); } } else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1 @@ -1987,7 +2046,6 @@ namespace MediaBrowser.Controller.MediaEncoding // http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/ var request = state.BaseRequest; - var videoStream = state.VideoStream; var filters = new List<string>(); @@ -1996,32 +2054,34 @@ namespace MediaBrowser.Controller.MediaEncoding var inputHeight = videoStream?.Height; var threeDFormat = state.MediaSource.Video3DFormat; + var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; + var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1; + var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1; + var isNvdecH264Decoder = videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) != -1; + var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1; + var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; // When the input may or may not be hardware VAAPI decodable - if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + if (isVaapiH264Encoder) { filters.Add("format=nv12|vaapi"); filters.Add("hwupload"); } - // When the input may or may not be hardware QSV decodable - else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + // When burning in graphical subtitles using overlay_qsv, upload videostream to the same qsv context + else if (isLinux && hasGraphicalSubs && isQsvH264Encoder) { - if (!hasTextSubs) - { - filters.Add("format=nv12|qsv"); - filters.Add("hwupload=extra_hw_frames=64"); - } + filters.Add("hwupload=extra_hw_frames=64"); } // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first - else if (videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 - && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) + else if (IsVaapiSupported(state) && isVaapiDecoder && isLibX264Encoder) { var codec = videoStream.Codec.ToLowerInvariant(); - var isColorDepth10 = !string.IsNullOrEmpty(videoStream.Profile) && (videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase) - || videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase)); + var isColorDepth10 = IsColorDepth10(state); // Assert 10-bit hardware VAAPI decodable if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) @@ -2046,49 +2106,49 @@ namespace MediaBrowser.Controller.MediaEncoding } // Add hardware deinterlace filter before scaling filter - if (state.DeInterlace("h264", true)) + if (state.DeInterlace("h264", true) || state.DeInterlace("avc", true)) { - if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + if (isVaapiH264Encoder) { filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_vaapi")); } - else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) - { - if (!hasTextSubs) - { - filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_qsv")); - } - } } // Add software deinterlace filter before scaling filter - if (((state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true)) - && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - && !string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) - || (hasTextSubs && state.DeInterlace("h264", true) && string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))) + if (state.DeInterlace("h264", true) + || state.DeInterlace("avc", true) + || state.DeInterlace("h265", true) + || state.DeInterlace("hevc", true)) { + string deintParam; var inputFramerate = videoStream?.RealFrameRate; // If it is already 60fps then it will create an output framerate that is much too high for roku and others to handle if (string.Equals(options.DeinterlaceMethod, "yadif_bob", StringComparison.OrdinalIgnoreCase) && (inputFramerate ?? 60) <= 30) { - filters.Add("yadif=1:-1:0"); + deintParam = "yadif=1:-1:0"; } else { - filters.Add("yadif=0:-1:0"); + deintParam = "yadif=0:-1:0"; + } + + if (!string.IsNullOrEmpty(deintParam)) + { + if (!isVaapiH264Encoder && !isQsvH264Encoder && !isNvdecH264Decoder) + { + filters.Add(deintParam); + } } } // Add scaling filter: scale_*=format=nv12 or scale_*=w=*:h=*:format=nv12 or scale=expr filters.AddRange(GetScalingFilters(state, inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight)); - // Add parameters to use VAAPI with burn-in text subttiles (GH issue #642) - if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + // Add parameters to use VAAPI with burn-in text subtitles (GH issue #642) + if (isVaapiH264Encoder) { - if (state.SubtitleStream != null - && state.SubtitleStream.IsTextSubtitleStream - && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) + if (hasTextSubs) { // Test passed on Intel and AMD gfx filters.Add("hwmap=mode=read+write"); @@ -2098,9 +2158,7 @@ namespace MediaBrowser.Controller.MediaEncoding var output = string.Empty; - if (state.SubtitleStream != null - && state.SubtitleStream.IsTextSubtitleStream - && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) + if (hasTextSubs) { var subParam = GetTextSubtitleParam(state); @@ -2108,7 +2166,7 @@ namespace MediaBrowser.Controller.MediaEncoding // Ensure proper filters are passed to ffmpeg in case of hardware acceleration via VA-API // Reference: https://trac.ffmpeg.org/wiki/Hardware/VAAPI - if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + if (isVaapiH264Encoder) { filters.Add("hwmap"); } @@ -2125,7 +2183,6 @@ namespace MediaBrowser.Controller.MediaEncoding return output; } - /// <summary> /// Gets the number of threads. /// </summary> @@ -2262,7 +2319,7 @@ namespace MediaBrowser.Controller.MediaEncoding flags.Add("+ignidx"); } - if (state.GenPtsInput || EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) + if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec)) { flags.Add("+genpts"); } @@ -2288,7 +2345,8 @@ namespace MediaBrowser.Controller.MediaEncoding { inputModifier += " " + videoDecoder; - if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1) + if (!IsCopyCodec(state.OutputVideoCodec) + && (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1) { var videoStream = state.VideoStream; var inputWidth = videoStream?.Width; @@ -2301,11 +2359,19 @@ namespace MediaBrowser.Controller.MediaEncoding && width.HasValue && height.HasValue) { - inputModifier += string.Format( - CultureInfo.InvariantCulture, - " -resize {0}x{1}", - width.Value, - height.Value); + if (width.HasValue && height.HasValue) + { + inputModifier += string.Format( + CultureInfo.InvariantCulture, + " -resize {0}x{1}", + width.Value, + height.Value); + } + + if (state.DeInterlace("h264", true)) + { + inputModifier += " -deint 1"; + } } } } @@ -2521,21 +2587,21 @@ namespace MediaBrowser.Controller.MediaEncoding } /// <summary> - /// Gets the name of the output video codec. + /// Gets the ffmpeg option string for the hardware accelerated video decoder. /// </summary> + /// <param name="state">The encoding job info.</param> + /// <param name="encodingOptions">The encoding options.</param> + /// <returns>The option string or null if none available.</returns> protected string GetHardwareAcceleratedVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions) { - var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile; var videoStream = state.VideoStream; - var isColorDepth10 = !string.IsNullOrEmpty(videoStream.Profile) && (videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase) - || videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase)); - - if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) + if (videoStream == null) { return null; } + var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile; // 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 // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. @@ -2544,10 +2610,15 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } - if (videoStream != null - && !string.IsNullOrEmpty(videoStream.Codec) - && !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType)) + if (IsCopyCodec(state.OutputVideoCodec)) { + return null; + } + + if (!string.IsNullOrEmpty(videoStream.Codec) && !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType)) + { + var isColorDepth10 = IsColorDepth10(state); + // Only hevc and vp9 formats have 10-bit hardware decoder support now. if (isColorDepth10 && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase) @@ -2603,6 +2674,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return "-c:v vp8_qsv"; } + break; case "vp9": if (_mediaEncoder.SupportsDecoder("vp9_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase)) @@ -2610,6 +2682,7 @@ namespace MediaBrowser.Controller.MediaEncoding return (isColorDepth10 && !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_qsv"; } + break; } } @@ -2621,13 +2694,6 @@ namespace MediaBrowser.Controller.MediaEncoding case "h264": if (_mediaEncoder.SupportsDecoder("h264_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase)) { - // cuvid decoder does not support 10-bit input. - if ((videoStream.BitDepth ?? 8) > 8) - { - encodingOptions.HardwareDecodingCodecs = Array.Empty<string>(); - return null; - } - return "-c:v h264_cuvid"; } @@ -2667,6 +2733,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return "-c:v vp8_cuvid"; } + break; case "vp9": if (_mediaEncoder.SupportsDecoder("vp9_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase)) @@ -2674,6 +2741,7 @@ namespace MediaBrowser.Controller.MediaEncoding return (isColorDepth10 && !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_cuvid"; } + break; } } @@ -2818,6 +2886,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return "-c:v h264_opencl"; } + break; case "hevc": case "h265": @@ -2826,30 +2895,35 @@ namespace MediaBrowser.Controller.MediaEncoding return (isColorDepth10 && !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_opencl"; } + break; case "mpeg2video": if (_mediaEncoder.SupportsDecoder("mpeg2_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase)) { return "-c:v mpeg2_opencl"; } + break; case "mpeg4": if (_mediaEncoder.SupportsDecoder("mpeg4_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase)) { return "-c:v mpeg4_opencl"; } + break; case "vc1": if (_mediaEncoder.SupportsDecoder("vc1_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase)) { return "-c:v vc1_opencl"; } + break; case "vp8": if (_mediaEncoder.SupportsDecoder("vp8_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase)) { return "-c:v vp8_opencl"; } + break; case "vp9": if (_mediaEncoder.SupportsDecoder("vp9_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase)) @@ -2857,6 +2931,7 @@ namespace MediaBrowser.Controller.MediaEncoding return (isColorDepth10 && !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_opencl"; } + break; } } @@ -2885,21 +2960,24 @@ namespace MediaBrowser.Controller.MediaEncoding /// </summary> public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec) { - var isWindows = Environment.OSVersion.Platform == PlatformID.Win32NT; + var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isWindows8orLater = Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1); var isDxvaSupported = _mediaEncoder.SupportsHwaccel("dxva2") || _mediaEncoder.SupportsHwaccel("d3d11va"); if ((isDxvaSupported || IsVaapiSupported(state)) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) { - if (!isWindows) + if (isLinux) { return "-hwaccel vaapi"; } - else if (isWindows8orLater) + + if (isWindows && isWindows8orLater) { return "-hwaccel d3d11va"; } - else + + if (isWindows && !isWindows8orLater) { return "-hwaccel dxva2"; } @@ -2989,7 +3067,7 @@ namespace MediaBrowser.Controller.MediaEncoding args += " -mpegts_m2ts_mode 1"; } - if (EncodingHelper.IsCopyCodec(videoCodec)) + if (IsCopyCodec(videoCodec)) { if (state.VideoStream != null && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) @@ -3091,7 +3169,7 @@ namespace MediaBrowser.Controller.MediaEncoding var args = "-codec:a:0 " + codec; - if (EncodingHelper.IsCopyCodec(codec)) + if (IsCopyCodec(codec)) { return args; } @@ -3168,5 +3246,42 @@ namespace MediaBrowser.Controller.MediaEncoding { return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase); } + + public static bool IsColorDepth10(EncodingJobInfo state) + { + var result = false; + var videoStream = state.VideoStream; + + if (videoStream != null) + { + if (!string.IsNullOrEmpty(videoStream.PixelFormat)) + { + result = videoStream.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase); + if (result) + { + return true; + } + } + + if (!string.IsNullOrEmpty(videoStream.Profile)) + { + result = videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase) + || videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase) + || videoStream.Profile.Contains("Profile 2", StringComparison.OrdinalIgnoreCase); + if (result) + { + return true; + } + } + + result = (videoStream.BitDepth ?? 8) == 10; + if (result) + { + return true; + } + } + + return result; + } } } |
