diff options
Diffstat (limited to 'MediaBrowser.MediaEncoding/Encoder')
| -rw-r--r-- | MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs | 86 | ||||
| -rw-r--r-- | MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs | 24 | ||||
| -rw-r--r-- | MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs | 2 | ||||
| -rw-r--r-- | MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 104 |
4 files changed, 129 insertions, 87 deletions
diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs index ce00acb52..e8121d178 100644 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs @@ -487,7 +487,15 @@ namespace MediaBrowser.MediaEncoding.Encoder var videoEncoder = EncodingJobFactory.GetVideoEncoder(MediaEncoder, state, encodingOptions); if (videoEncoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1) { - arg = "-hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device " + encodingOptions.VaapiDevice + " " + arg; + var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.Options.SubtitleMethod == SubtitleDeliveryMethod.Encode; + var hwOutputFormat = "vaapi"; + + if (hasGraphicalSubs) + { + hwOutputFormat = "yuv420p"; + } + + arg = "-hwaccel vaapi -hwaccel_output_format " + hwOutputFormat + " -vaapi_device " + encodingOptions.VaapiDevice + " " + arg; } } @@ -565,7 +573,20 @@ namespace MediaBrowser.MediaEncoding.Encoder { outputSizeParam = await GetOutputSizeParam(state, outputVideoCodec).ConfigureAwait(false); outputSizeParam = outputSizeParam.TrimEnd('"'); - outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase)); + + if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + { + outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase)); + } + else + { + outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase)); + } + } + + if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && outputSizeParam.Length == 0) + { + outputSizeParam = ",format=nv12|vaapi,hwupload"; } var videoSizeParam = string.Empty; @@ -595,23 +616,23 @@ namespace MediaBrowser.MediaEncoding.Encoder /// Gets the video bitrate to specify on the command line /// </summary> /// <param name="state">The state.</param> - /// <param name="videoCodec">The video codec.</param> + /// <param name="videoEncoder">The video codec.</param> /// <returns>System.String.</returns> - protected string GetVideoQualityParam(EncodingJob state, string videoCodec) + protected string GetVideoQualityParam(EncodingJob state, string videoEncoder) { var param = string.Empty; var isVc1 = state.VideoStream != null && string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase); - if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) { param = "-preset superfast"; param += " -crf 23"; } - else if (string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) { param = "-preset fast"; @@ -619,20 +640,20 @@ namespace MediaBrowser.MediaEncoding.Encoder } // h264 (h264_qsv) - else if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)) { param = "-preset 7 -look_ahead 0"; } // h264 (h264_nvenc) - else if (string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) { param = "-preset llhq"; } // webm - else if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) { // Values 0-3, 0 being highest quality but slower var profileScore = 0; @@ -659,23 +680,23 @@ namespace MediaBrowser.MediaEncoding.Encoder qmax); } - else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "mpeg4", StringComparison.OrdinalIgnoreCase)) { param = "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2"; } // asf/wmv - else if (string.Equals(videoCodec, "wmv2", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "wmv2", StringComparison.OrdinalIgnoreCase)) { param = "-qmin 2"; } - else if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "msmpeg4", StringComparison.OrdinalIgnoreCase)) { param = "-mbd 2"; } - param += GetVideoBitrateParam(state, videoCodec); + param += GetVideoBitrateParam(state, videoEncoder); var framerate = GetFramerateParam(state); if (framerate.HasValue) @@ -690,8 +711,8 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!string.IsNullOrEmpty(state.Options.Profile)) { - if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) && + !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) { // not supported by h264_omx param += " -profile:v " + state.Options.Profile; @@ -702,9 +723,11 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!string.IsNullOrEmpty(levelString)) { + levelString = NormalizeTranscodingLevel(state.OutputVideoCodec, levelString); + // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format - if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) || - string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || + string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) { switch (levelString) { @@ -740,16 +763,16 @@ namespace MediaBrowser.MediaEncoding.Encoder break; } } - else if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase)) + else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)) { param += " -level " + levelString; } } - if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) && + !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && + !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) && + !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) { param = "-pix_fmt yuv420p " + param; } @@ -757,6 +780,25 @@ namespace MediaBrowser.MediaEncoding.Encoder return param; } + private string NormalizeTranscodingLevel(string videoCodec, string level) + { + double requestLevel; + + // Clients may direct play higher than level 41, but there's no reason to transcode higher + if (double.TryParse(level, NumberStyles.Any, UsCulture, out requestLevel)) + { + if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)) + { + if (requestLevel > 41) + { + return "41"; + } + } + } + + return level; + } + protected string GetVideoBitrateParam(EncodingJob state, string videoCodec) { var bitrate = state.OutputVideoBitrate; diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index a450097fd..24de4e77e 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -26,6 +26,30 @@ namespace MediaBrowser.MediaEncoding.Encoder return new Tuple<List<string>, List<string>>(decoders, encoders); } + public bool ValidateVersion(string encoderAppPath) + { + string output = string.Empty; + try + { + output = GetProcessOutput(encoderAppPath, "-version"); + } + catch + { + } + + if (string.IsNullOrWhiteSpace(output)) + { + return false; + } + + if (output.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1) + { + return false; + } + + return true; + } + private List<string> GetDecoders(string encoderAppPath) { string output = string.Empty; diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs index 490a51128..d3738d903 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs @@ -140,7 +140,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { try { - await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false); + await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 75d85cd32..06867969e 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -82,6 +82,8 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>(); private readonly bool _hasExternalEncoder; + private string _originalFFMpegPath; + private string _originalFFProbePath; public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func<ISubtitleEncoder> subtitleEncoder, Func<IMediaSourceManager> mediaSourceManager, IHttpClient httpClient, IZipClient zipClient) { @@ -100,6 +102,8 @@ namespace MediaBrowser.MediaEncoding.Encoder _zipClient = zipClient; FFProbePath = ffProbePath; FFMpegPath = ffMpegPath; + _originalFFProbePath = ffProbePath; + _originalFFMpegPath = ffMpegPath; _hasExternalEncoder = hasExternalEncoder; } @@ -108,11 +112,6 @@ namespace MediaBrowser.MediaEncoding.Encoder { get { - if (_hasExternalEncoder) - { - return "External"; - } - if (string.IsNullOrWhiteSpace(FFMpegPath)) { return null; @@ -177,12 +176,6 @@ namespace MediaBrowser.MediaEncoding.Encoder { ConfigureEncoderPaths(); - if (_hasExternalEncoder) - { - LogPaths(); - return; - } - // If the path was passed in, save it into config now. var encodingOptions = GetEncodingOptions(); var appPath = encodingOptions.EncoderAppPath; @@ -207,11 +200,6 @@ namespace MediaBrowser.MediaEncoding.Encoder public async Task UpdateEncoderPath(string path, string pathType) { - if (_hasExternalEncoder) - { - return; - } - Tuple<string, string> newPaths; if (string.Equals(pathType, "system", StringComparison.OrdinalIgnoreCase)) @@ -247,6 +235,13 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new ResourceNotFoundException("ffprobe not found"); } + path = newPaths.Item1; + + if (!ValidateVersion(path)) + { + throw new ResourceNotFoundException("ffmpeg version 3.0 or greater is required."); + } + var config = GetEncodingOptions(); config.EncoderAppPath = path; ConfigurationManager.SaveConfiguration("encoding", config); @@ -254,13 +249,13 @@ namespace MediaBrowser.MediaEncoding.Encoder Init(); } - private void ConfigureEncoderPaths() + private bool ValidateVersion(string path) { - if (_hasExternalEncoder) - { - return; - } + return new EncoderValidator(_logger).ValidateVersion(path); + } + private void ConfigureEncoderPaths() + { var appPath = GetEncodingOptions().EncoderAppPath; if (string.IsNullOrWhiteSpace(appPath)) @@ -308,45 +303,22 @@ namespace MediaBrowser.MediaEncoding.Encoder string encoderPath = null; string probePath = null; - if (TestSystemInstalled("ffmpeg")) - { - encoderPath = "ffmpeg"; - } - if (TestSystemInstalled("ffprobe")) + if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath)) { - probePath = "ffprobe"; + encoderPath = _originalFFMpegPath; + probePath = _originalFFProbePath; } - return new Tuple<string, string>(encoderPath, probePath); - } - - private bool TestSystemInstalled(string app) - { - try + if (string.IsNullOrWhiteSpace(encoderPath)) { - var startInfo = new ProcessStartInfo - { - FileName = app, - Arguments = "-v", - UseShellExecute = false, - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false - }; - - using (var process = Process.Start(startInfo)) + if (ValidateVersion("ffmpeg") && ValidateVersion("ffprobe")) { - process.WaitForExit(); + encoderPath = "ffmpeg"; + probePath = "ffprobe"; } - - _logger.Debug("System app installed: " + app); - return true; - } - catch - { - _logger.Debug("System app not installed: " + app); - return false; } + + return new Tuple<string, string>(encoderPath, probePath); } private Tuple<string, string> GetPathsFromDirectory(string path) @@ -799,20 +771,20 @@ namespace MediaBrowser.MediaEncoding.Encoder public Task<string> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken) { - return ExtractImage(new[] { path }, imageStreamIndex, MediaProtocol.File, true, null, null, cancellationToken); + return ExtractImage(new[] { path }, null, imageStreamIndex, MediaProtocol.File, true, null, null, cancellationToken); } - public Task<string> ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) + public Task<string> ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) { - return ExtractImage(inputFiles, null, protocol, false, threedFormat, offset, cancellationToken); + return ExtractImage(inputFiles, container, null, protocol, false, threedFormat, offset, cancellationToken); } - public Task<string> ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, int? imageStreamIndex, CancellationToken cancellationToken) + public Task<string> ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, int? imageStreamIndex, CancellationToken cancellationToken) { - return ExtractImage(inputFiles, imageStreamIndex, protocol, false, null, null, cancellationToken); + return ExtractImage(inputFiles, container, imageStreamIndex, protocol, false, null, null, cancellationToken); } - private async Task<string> ExtractImage(string[] inputFiles, int? imageStreamIndex, MediaProtocol protocol, bool isAudio, + private async Task<string> ExtractImage(string[] inputFiles, string container, int? imageStreamIndex, MediaProtocol protocol, bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) { var resourcePool = isAudio ? _audioImageResourcePool : _videoImageResourcePool; @@ -831,7 +803,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { try { - return await ExtractImageInternal(inputArgument, imageStreamIndex, protocol, threedFormat, offset, true, resourcePool, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, imageStreamIndex, protocol, threedFormat, offset, true, resourcePool, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -843,10 +815,10 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - return await ExtractImageInternal(inputArgument, imageStreamIndex, protocol, threedFormat, offset, false, resourcePool, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, imageStreamIndex, protocol, threedFormat, offset, false, resourcePool, cancellationToken).ConfigureAwait(false); } - private async Task<string> ExtractImageInternal(string inputPath, int? imageStreamIndex, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, SemaphoreSlim resourcePool, CancellationToken cancellationToken) + private async Task<string> ExtractImageInternal(string inputPath, string container, int? imageStreamIndex, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, SemaphoreSlim resourcePool, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(inputPath)) { @@ -887,8 +859,11 @@ namespace MediaBrowser.MediaEncoding.Encoder var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty; + var enableThumbnail = !new List<string> { "wtv" }.Contains(container ?? string.Empty, StringComparer.OrdinalIgnoreCase); + var thumbnail = enableThumbnail ? ",thumbnail=30" : string.Empty; + // 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. - var args = useIFrame ? string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2},thumbnail=30\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg) : + var args = useIFrame ? string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}{4}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail) : string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg); var probeSize = GetProbeSizeArgument(new[] { inputPath }, protocol); @@ -931,8 +906,9 @@ namespace MediaBrowser.MediaEncoding.Encoder var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs; if (timeoutMs <= 0) { - timeoutMs = 10000; + timeoutMs = Environment.Is64BitOperatingSystem ? (Environment.ProcessorCount > 2 ? 14000 : 20000) : 40000; } + ranToCompletion = process.WaitForExit(timeoutMs); if (!ranToCompletion) |
