diff options
| author | Mathieu Velten <matmaul@gmail.com> | 2019-01-13 02:06:59 +0100 |
|---|---|---|
| committer | Mathieu Velten <matmaul@gmail.com> | 2019-01-20 22:50:09 +0100 |
| commit | da16de48aad03f4f32b05e9e25016bcec529f18b (patch) | |
| tree | 58faede5de482cd6f2a11787dcd26ae49b4ec2e8 /MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | |
| parent | 0586598d33ea5eeed36259fe970a7e3184f6b087 (diff) | |
Revert back to 10e57ce8d21b4516733894075001819f3cd6db6b for MediaEncoding
Remove some duplicate code that were causing warnings
Diffstat (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs')
| -rw-r--r-- | MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 267 |
1 files changed, 191 insertions, 76 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index edc43ef46..f7b9be806 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.Threading; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; @@ -21,6 +22,8 @@ namespace MediaBrowser.Controller.MediaEncoding private readonly IMediaEncoder _mediaEncoder; private readonly IFileSystem _fileSystem; private readonly ISubtitleEncoder _subtitleEncoder; + // private readonly IApplicationPaths _appPaths; + // private readonly IAssemblyInfo _assemblyInfo; public EncodingHelper(IMediaEncoder mediaEncoder, IFileSystem fileSystem, ISubtitleEncoder subtitleEncoder) { @@ -40,56 +43,55 @@ namespace MediaBrowser.Controller.MediaEncoding { var hwType = encodingOptions.HardwareAccelerationType; - if (!encodingOptions.EnableHardwareEncoding) + var codecMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { - hwType = null; - } - - if (string.Equals(hwType, "qsv", StringComparison.OrdinalIgnoreCase) || - string.Equals(hwType, "h264_qsv", StringComparison.OrdinalIgnoreCase)) - { - return GetAvailableEncoder("h264_qsv", defaultEncoder); - } + {"qsv", "h264_qsv"}, + {"h264_qsv", "h264_qsv"}, + {"nvenc", "h264_nvenc"}, + {"amf", "h264_amf"}, + {"omx", "h264_omx"}, + {"h264_v4l2m2m", "h264_v4l2m2m"}, + {"mediacodec", "h264_mediacodec"}, + {"vaapi", "h264_vaapi"} + }; - if (string.Equals(hwType, "nvenc", StringComparison.OrdinalIgnoreCase)) - { - return GetAvailableEncoder("h264_nvenc", defaultEncoder); - } - if (string.Equals(hwType, "amf", StringComparison.OrdinalIgnoreCase)) - { - return GetAvailableEncoder("h264_amf", defaultEncoder); - } - if (string.Equals(hwType, "omx", StringComparison.OrdinalIgnoreCase)) - { - return GetAvailableEncoder("h264_omx", defaultEncoder); - } - if (string.Equals(hwType, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) - { - return GetAvailableEncoder("h264_v4l2m2m", defaultEncoder); - } - if (string.Equals(hwType, "mediacodec", StringComparison.OrdinalIgnoreCase)) - { - return GetAvailableEncoder("h264_mediacodec", defaultEncoder); - } - if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(encodingOptions.VaapiDevice)) + if (!string.IsNullOrEmpty(hwType) + && encodingOptions.EnableHardwareEncoding && codecMap.ContainsKey(hwType)) { - if (IsVaapiSupported(state)) + if (CheckVaapi(state, hwType, encodingOptions)) { - return GetAvailableEncoder("h264_vaapi", defaultEncoder); + var preferredEncoder = codecMap[hwType]; + + if (_mediaEncoder.SupportsEncoder(preferredEncoder)) + { + return preferredEncoder; + } } } + } + // Avoid performing a second attempt when the first one + // hasn't tried hardware encoding anyway. + encodingOptions.EnableHardwareEncoding = false; return defaultEncoder; } - private string GetAvailableEncoder(string preferredEncoder, string defaultEncoder) + private bool CheckVaapi(EncodingJobInfo state, string hwType, EncodingOptions encodingOptions) { - if (_mediaEncoder.SupportsEncoder(preferredEncoder)) + if (!string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase)) { - return preferredEncoder; + // No vaapi requested, return OK. + return true; } - return defaultEncoder; + + if (string.IsNullOrEmpty(encodingOptions.VaapiDevice)) + { + // No device specified, return OK. + return true; + } + + return IsVaapiSupported(state); } private bool IsVaapiSupported(EncodingJobInfo state) @@ -340,7 +342,7 @@ namespace MediaBrowser.Controller.MediaEncoding public int GetVideoProfileScore(string profile) { - string[] list = + var list = new[] { "ConstrainedBaseline", "Baseline", @@ -538,14 +540,54 @@ namespace MediaBrowser.Controller.MediaEncoding ? string.Empty : string.Format(",setpts=PTS -{0}/TB", seconds.ToString(_usCulture)); - string fallbackFontParam = string.Empty; + // TODO + // var fallbackFontPath = Path.Combine(_appPaths.ProgramDataPath, "fonts", "DroidSansFallback.ttf"); + // string fallbackFontParam = string.Empty; + + // if (!_fileSystem.FileExists(fallbackFontPath)) + // { + // _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(fallbackFontPath)); + // using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), GetType().Namespace + ".DroidSansFallback.ttf")) + // { + // using (var fileStream = _fileSystem.GetFileStream(fallbackFontPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) + // { + // stream.CopyTo(fileStream); + // } + // } + // } + + // fallbackFontParam = string.Format(":force_style='FontName=Droid Sans Fallback':fontsdir='{0}'", _mediaEncoder.EscapeSubtitleFilterPath(_fileSystem.GetDirectoryName(fallbackFontPath))); + + if (state.SubtitleStream.IsExternal) + { + var subtitlePath = state.SubtitleStream.Path; + + var charsetParam = string.Empty; + + if (!string.IsNullOrEmpty(state.SubtitleStream.Language)) + { + var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).Result; + + if (!string.IsNullOrEmpty(charenc)) + { + charsetParam = ":charenc=" + charenc; + } + } + + // TODO: Perhaps also use original_size=1920x800 ?? + return string.Format("subtitles=filename='{0}'{1}{2}{3}", + _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath), + charsetParam, + // fallbackFontParam, + setPtsParam); + } var mediaPath = state.MediaPath ?? string.Empty; - return string.Format("subtitles='{0}:si={1}'{2}{3}", + return string.Format("subtitles='{0}:si={1}'{2}", _mediaEncoder.EscapeSubtitleFilterPath(mediaPath), state.InternalSubtitleStreamOffset.ToString(_usCulture), - fallbackFontParam, + // fallbackFontParam, setPtsParam); } @@ -1039,51 +1081,72 @@ namespace MediaBrowser.Controller.MediaEncoding { var bitrate = request.VideoBitRate; + // If specific values were requested, then force the caller to supply a bitrate as well + if (request.Height.HasValue && request.Width.HasValue) + { + return bitrate; + } + if (videoStream != null) { - var isUpscaling = request.Height.HasValue && videoStream.Height.HasValue && - request.Height.Value > videoStream.Height.Value && request.Width.HasValue && videoStream.Width.HasValue && - request.Width.Value > videoStream.Width.Value; + if (bitrate.HasValue && videoStream.BitRate.HasValue) + { + bitrate = Math.Min(videoStream.BitRate.Value, bitrate.Value); + } - // Don't allow bitrate increases unless upscaling - if (!isUpscaling) + if (bitrate.HasValue) { - if (bitrate.HasValue && videoStream.BitRate.HasValue) + var inputVideoCodec = videoStream.Codec; + bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec); + + // If a max bitrate was requested, don't let the scaled bitrate exceed it + if (request.VideoBitRate.HasValue) { - bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value); + bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value); } } } - if (bitrate.HasValue) - { - var inputVideoCodec = videoStream == null ? null : videoStream.Codec; - bitrate = ResolutionNormalizer.ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec); + return bitrate; + } - // If a max bitrate was requested, don't let the scaled bitrate exceed it - if (request.VideoBitRate.HasValue) - { - bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value); - } + private static double GetVideoBitrateScaleFactor(string codec) + { + if (StringHelper.EqualsIgnoreCase(codec, "h265") || + StringHelper.EqualsIgnoreCase(codec, "hevc") || + StringHelper.EqualsIgnoreCase(codec, "vp9")) + { + return .5; } - - return bitrate; + return 1; } - private int GetMinBitrate(int sourceBitrate, int requestedBitrate) + private static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec) { - if (sourceBitrate <= 2000000) + var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec); + var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec); + var scaleFactor = outputScaleFactor / inputScaleFactor; + + if (bitrate <= 500000) + { + scaleFactor = Math.Max(scaleFactor, 4); + } + else if (bitrate <= 1000000) + { + scaleFactor = Math.Max(scaleFactor, 3); + } + else if (bitrate <= 2000000) { - sourceBitrate = Convert.ToInt32(sourceBitrate * 2.5); + scaleFactor = Math.Max(scaleFactor, 2.5); } - else if (sourceBitrate <= 3000000) + else if (bitrate <= 3000000) { - sourceBitrate = Convert.ToInt32(sourceBitrate * 2); + scaleFactor = Math.Max(scaleFactor, 2); } - var bitrate = Math.Min(sourceBitrate, requestedBitrate); + var newBitrate = scaleFactor * bitrate; - return bitrate; + return Convert.ToInt32(newBitrate); } public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream) @@ -1405,7 +1468,7 @@ namespace MediaBrowser.Controller.MediaEncoding videoSizeParam); } - private Tuple<int?, int?> GetFixedOutputSize(int? videoWidth, + private ValueTuple<int?, int?> GetFixedOutputSize(int? videoWidth, int? videoHeight, int? requestedWidth, int? requestedHeight, @@ -1414,11 +1477,11 @@ namespace MediaBrowser.Controller.MediaEncoding { if (!videoWidth.HasValue && !requestedWidth.HasValue) { - return new Tuple<int?, int?>(null, null); + return new ValueTuple<int?, int?>(null, null); } if (!videoHeight.HasValue && !requestedHeight.HasValue) { - return new Tuple<int?, int?>(null, null); + return new ValueTuple<int?, int?>(null, null); } decimal inputWidth = Convert.ToDecimal(videoWidth ?? requestedWidth); @@ -1438,7 +1501,7 @@ namespace MediaBrowser.Controller.MediaEncoding outputWidth = 2 * Math.Truncate(outputWidth / 2); outputHeight = 2 * Math.Truncate(outputHeight / 2); - return new Tuple<int?, int?>(Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight)); + return new ValueTuple<int?, int?>(Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight)); } public List<string> GetScalingFilters(int? videoWidth, @@ -1669,7 +1732,7 @@ namespace MediaBrowser.Controller.MediaEncoding var inputHeight = videoStream == null ? null : videoStream.Height; var threeDFormat = state.MediaSource.Video3DFormat; - var videoDecoder = GetVideoDecoder(state, options); + var videoDecoder = this.GetHardwareAcceleratedVideoDecoder(state, options); filters.AddRange(GetScalingFilters(inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight)); @@ -1851,7 +1914,7 @@ namespace MediaBrowser.Controller.MediaEncoding inputModifier += " -fflags " + string.Join("", flags.ToArray()); } - var videoDecoder = GetVideoDecoder(state, encodingOptions); + var videoDecoder = this.GetHardwareAcceleratedVideoDecoder(state, encodingOptions); if (!string.IsNullOrEmpty(videoDecoder)) { inputModifier += " " + videoDecoder; @@ -1893,7 +1956,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.MediaSource.RequiresLooping) { - inputModifier += " -stream_loop -1"; + inputModifier += " -stream_loop -1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2"; } return inputModifier; @@ -1989,7 +2052,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (string.IsNullOrEmpty(requestedUrl)) { - requestedUrl = "test." + videoRequest.OutputContainer; + requestedUrl = "test." + videoRequest.Container; } videoRequest.VideoCodec = InferVideoCodec(requestedUrl); @@ -2015,6 +2078,49 @@ namespace MediaBrowser.Controller.MediaEncoding } state.MediaSource = mediaSource; + + var request = state.BaseRequest; + if (!string.IsNullOrWhiteSpace(request.AudioCodec)) + { + var supportedAudioCodecsList = request.AudioCodec.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); + + ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream); + + state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray(); + + request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => _mediaEncoder.CanEncodeToAudioCodec(i)) + ?? state.SupportedAudioCodecs.FirstOrDefault(); + } + } + + private void ShiftAudioCodecsIfNeeded(List<string> audioCodecs, MediaStream audioStream) + { + // Nothing to do here + if (audioCodecs.Count < 2) + { + return; + } + + var inputChannels = audioStream == null ? 6 : audioStream.Channels ?? 6; + if (inputChannels >= 6) + { + return; + } + + // Transcoding to 2ch ac3 almost always causes a playback failure + // Keep it in the supported codecs list, but shift it to the end of the list so that if transcoding happens, another codec is used + var shiftAudioCodecs = new[] { "ac3", "eac3" }; + if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparer.OrdinalIgnoreCase))) + { + return; + } + + while (shiftAudioCodecs.Contains(audioCodecs[0], StringComparer.OrdinalIgnoreCase)) + { + var removed = shiftAudioCodecs[0]; + audioCodecs.RemoveAt(0); + audioCodecs.Add(removed); + } } private void NormalizeSubtitleEmbed(EncodingJobInfo state) @@ -2035,17 +2141,17 @@ namespace MediaBrowser.Controller.MediaEncoding /// <summary> /// Gets the name of the output video codec /// </summary> - protected string GetVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions) + protected string GetHardwareAcceleratedVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions) { if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { return null; } - return GetVideoDecoder(state.MediaSource.VideoType ?? VideoType.VideoFile, state.VideoStream, encodingOptions); + return this.GetHardwareAcceleratedVideoDecoder(state.MediaSource.VideoType ?? VideoType.VideoFile, state.VideoStream, encodingOptions); } - public string GetVideoDecoder(VideoType videoType, MediaStream videoStream, EncodingOptions encodingOptions) + public string GetHardwareAcceleratedVideoDecoder(VideoType videoType, MediaStream videoStream, 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 @@ -2070,6 +2176,7 @@ namespace MediaBrowser.Controller.MediaEncoding // qsv decoder does not support 10-bit input if ((videoStream.BitDepth ?? 8) > 8) { + encodingOptions.HardwareDecodingCodecs = Array.Empty<string>(); return null; } return "-c:v h264_qsv "; @@ -2204,6 +2311,11 @@ namespace MediaBrowser.Controller.MediaEncoding else if (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) { + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + return "-hwaccel dxva2"; + } + switch (videoStream.Codec.ToLower()) { case "avc": @@ -2223,6 +2335,9 @@ namespace MediaBrowser.Controller.MediaEncoding } } + // Avoid a second attempt if no hardware acceleration is being used + encodingOptions.HardwareDecodingCodecs = Array.Empty<string>(); + // leave blank so ffmpeg will decide return null; } |
