From da16de48aad03f4f32b05e9e25016bcec529f18b Mon Sep 17 00:00:00 2001 From: Mathieu Velten Date: Sun, 13 Jan 2019 02:06:59 +0100 Subject: [PATCH 1/3] Revert back to 10e57ce8d21b4516733894075001819f3cd6db6b for MediaEncoding Remove some duplicate code that were causing warnings --- MediaBrowser.Api/Playback/StreamRequest.cs | 23 -- MediaBrowser.Api/Playback/StreamState.cs | 106 ++----- .../MediaEncoding/EncodingHelper.cs | 271 +++++++++++++----- .../MediaEncoding/EncodingJobInfo.cs | 73 +++-- .../MediaEncoding/EncodingJobOptions.cs | 41 ++- .../Encoder/EncodingJob.cs | 66 +---- 6 files changed, 309 insertions(+), 271 deletions(-) diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index da1f00c3e..7626cc378 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -9,29 +9,6 @@ namespace MediaBrowser.Api.Playback /// public class StreamRequest : BaseEncodingJobOptions { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string MediaSourceId { get; set; } - - [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string DeviceId { get; set; } - - [ApiMember(Name = "Container", Description = "Container", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Container { get; set; } - - /// - /// Gets or sets the audio codec. - /// - /// The audio codec. - [ApiMember(Name = "AudioCodec", Description = "Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string AudioCodec { get; set; } - [ApiMember(Name = "DeviceProfileId", Description = "Optional. The dlna device profile id to utilize.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string DeviceProfileId { get; set; } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 400ad47cc..96dc4ab4c 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -92,62 +92,29 @@ namespace MediaBrowser.Api.Playback } } - public int HlsListSize => 0; - public string UserAgent { get; set; } public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger, TranscodingJobType transcodingType) - : base(logger, mediaSourceManager, transcodingType) + : base(transcodingType) { _mediaSourceManager = mediaSourceManager; _logger = logger; } - public string MimeType { get; set; } - public bool EstimateContentLength { get; set; } public TranscodeSeekInfo TranscodeSeekInfo { get; set; } - public long? EncodingDurationTicks { get; set; } - - public string GetMimeType(string outputPath, bool enableStreamDefault = true) - { - if (!string.IsNullOrEmpty(MimeType)) - { - return MimeType; - } - - return MimeTypes.GetMimeType(outputPath, enableStreamDefault); - } - public bool EnableDlnaHeaders { get; set; } - public void Dispose() + public override void Dispose() { DisposeTranscodingThrottler(); - DisposeLiveStream(); DisposeLogStream(); + DisposeLiveStream(); TranscodingJob = null; } - private void DisposeLogStream() - { - if (LogFileStream != null) - { - try - { - LogFileStream.Dispose(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error disposing log stream"); - } - - LogFileStream = null; - } - } - private void DisposeTranscodingThrottler() { if (TranscodingThrottler != null) @@ -165,6 +132,23 @@ namespace MediaBrowser.Api.Playback } } + private void DisposeLogStream() + { + if (LogFileStream != null) + { + try + { + LogFileStream.Dispose(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error disposing log stream"); + } + + LogFileStream = null; + } + } + private async void DisposeLiveStream() { if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId)) @@ -180,58 +164,12 @@ namespace MediaBrowser.Api.Playback } } - public string OutputFilePath { get; set; } - - public string ActualOutputVideoCodec - { - get - { - var codec = OutputVideoCodec; - - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) - { - var stream = VideoStream; - - if (stream != null) - { - return stream.Codec; - } - - return null; - } - - return codec; - } - } - - public string ActualOutputAudioCodec - { - get - { - var codec = OutputAudioCodec; - - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) - { - var stream = AudioStream; - - if (stream != null) - { - return stream.Codec; - } - - return null; - } - - return codec; - } - } - public DeviceProfile DeviceProfile { get; set; } public TranscodingJob TranscodingJob; - public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float framerate, double? percentComplete, long bytesTranscoded, int? bitRate) + public void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) { - ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, 0, percentComplete, 0, bitRate); + ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate); } } } 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(StringComparer.OrdinalIgnoreCase) { - hwType = null; - } + {"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, "qsv", StringComparison.OrdinalIgnoreCase) || - string.Equals(hwType, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(hwType) + && encodingOptions.EnableHardwareEncoding && codecMap.ContainsKey(hwType)) { - return GetAvailableEncoder("h264_qsv", defaultEncoder); - } - - 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 (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 (videoStream != null) + // If specific values were requested, then force the caller to supply a bitrate as well + if (request.Height.HasValue && request.Width.HasValue) { - 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; - - // Don't allow bitrate increases unless upscaling - if (!isUpscaling) - { - if (bitrate.HasValue && videoStream.BitRate.HasValue) - { - bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value); - } - } + return bitrate; } - if (bitrate.HasValue) + if (videoStream != null) { - var inputVideoCodec = videoStream == null ? null : videoStream.Codec; - bitrate = ResolutionNormalizer.ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec); - - // If a max bitrate was requested, don't let the scaled bitrate exceed it - if (request.VideoBitRate.HasValue) + if (bitrate.HasValue && videoStream.BitRate.HasValue) { - bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value); + bitrate = Math.Min(videoStream.BitRate.Value, bitrate.Value); + } + + if (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 = Math.Min(bitrate.Value, request.VideoBitRate.Value); + } } } return bitrate; } - private int GetMinBitrate(int sourceBitrate, int requestedBitrate) + private static double GetVideoBitrateScaleFactor(string codec) { - if (sourceBitrate <= 2000000) + if (StringHelper.EqualsIgnoreCase(codec, "h265") || + StringHelper.EqualsIgnoreCase(codec, "hevc") || + StringHelper.EqualsIgnoreCase(codec, "vp9")) { - sourceBitrate = Convert.ToInt32(sourceBitrate * 2.5); + return .5; } - else if (sourceBitrate <= 3000000) + return 1; + } + + private static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec) + { + var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec); + var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec); + var scaleFactor = outputScaleFactor / inputScaleFactor; + + if (bitrate <= 500000) { - sourceBitrate = Convert.ToInt32(sourceBitrate * 2); + scaleFactor = Math.Max(scaleFactor, 4); + } + else if (bitrate <= 1000000) + { + scaleFactor = Math.Max(scaleFactor, 3); + } + else if (bitrate <= 2000000) + { + scaleFactor = Math.Max(scaleFactor, 2.5); + } + else if (bitrate <= 3000000) + { + 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 GetFixedOutputSize(int? videoWidth, + private ValueTuple 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(null, null); + return new ValueTuple(null, null); } if (!videoHeight.HasValue && !requestedHeight.HasValue) { - return new Tuple(null, null); + return new ValueTuple(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(Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight)); + return new ValueTuple(Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight)); } public List 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 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 /// /// Gets the name of the output video codec /// - 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(); 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(); + // leave blank so ffmpeg will decide return null; } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index ea8a79306..6651a6d70 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; @@ -12,13 +11,17 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; +using System.IO; +using MediaBrowser.Model.Net; +using MediaBrowser.Controller.Library; +using System.Threading.Tasks; namespace MediaBrowser.Controller.MediaEncoding { // For now, a common base class until the API and MediaEncoding classes are unified - public abstract class EncodingJobInfo + public class EncodingJobInfo { - private readonly ILogger _logger; + protected readonly IMediaSourceManager MediaSourceManager; public MediaStream VideoStream { get; set; } public VideoType VideoType { get; set; } @@ -43,6 +46,21 @@ namespace MediaBrowser.Controller.MediaEncoding public bool ReadInputAtNativeFramerate { get; set; } + public string OutputFilePath { get; set; } + + public string MimeType { get; set; } + public long? EncodingDurationTicks { get; set; } + + public string GetMimeType(string outputPath, bool enableStreamDefault = true) + { + if (!string.IsNullOrEmpty(MimeType)) + { + return MimeType; + } + + return MimeTypes.GetMimeType(outputPath, enableStreamDefault); + } + private TranscodeReason[] _transcodeReasons = null; public TranscodeReason[] TranscodeReasons { @@ -266,9 +284,8 @@ namespace MediaBrowser.Controller.MediaEncoding public bool IsVideoRequest { get; set; } public TranscodingJobType TranscodingType { get; set; } - public EncodingJobInfo(ILogger logger, IMediaSourceManager unused, TranscodingJobType jobType) + public EncodingJobInfo(TranscodingJobType jobType) { - _logger = logger; TranscodingType = jobType; RemoteHttpHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); PlayableStreamFileNames = Array.Empty(); @@ -602,6 +619,28 @@ namespace MediaBrowser.Controller.MediaEncoding } } + public string ActualOutputAudioCodec + { + get + { + var codec = OutputAudioCodec; + + if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) + { + var stream = AudioStream; + + if (stream != null) + { + return stream.Codec; + } + + return null; + } + + return codec; + } + } + public bool? IsTargetInterlaced { get @@ -657,6 +696,14 @@ namespace MediaBrowser.Controller.MediaEncoding } } + public int HlsListSize + { + get + { + return 0; + } + } + private int? GetMediaStreamCount(MediaStreamType type, int limit) { var count = MediaSource.GetStreamCount(type); @@ -668,22 +715,6 @@ namespace MediaBrowser.Controller.MediaEncoding return count; } - protected void DisposeIsoMount() - { - if (IsoMount != null) - { - try - { - IsoMount.Dispose(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error disposing iso mount"); - } - - IsoMount = null; - } - } public IProgress Progress { get; set; } public virtual void ReportTranscodingProgress(TimeSpan? transcodingPosition, float framerate, double? percentComplete, long bytesTranscoded, int? bitRate) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs index ff54bb393..be97c75a2 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs @@ -11,9 +11,8 @@ namespace MediaBrowser.Controller.MediaEncoding { public string OutputDirectory { get; set; } public string ItemId { get; set; } - public string MediaSourceId { get; set; } - public string AudioCodec { get; set; } + public string TempDirectory { get; set; } public bool ReadInputAtNativeFramerate { get; set; } /// @@ -22,15 +21,16 @@ namespace MediaBrowser.Controller.MediaEncoding /// true if this instance has fixed resolution; otherwise, false. public bool HasFixedResolution => Width.HasValue || Height.HasValue; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + public DeviceProfile DeviceProfile { get; set; } + public EncodingJobOptions(StreamInfo info, DeviceProfile deviceProfile) { - OutputContainer = info.Container; + Container = info.Container; StartTimeTicks = info.StartPositionTicks; MaxWidth = info.MaxWidth; MaxHeight = info.MaxHeight; MaxFramerate = info.MaxFramerate; - ItemId = info.ItemId.ToString(""); + Id = info.ItemId; MediaSourceId = info.MediaSourceId; AudioCodec = info.TargetAudioCodec.FirstOrDefault(); MaxAudioChannels = info.GlobalMaxAudioChannels; @@ -55,6 +55,29 @@ namespace MediaBrowser.Controller.MediaEncoding // For now until api and media encoding layers are unified public class BaseEncodingJobOptions { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public Guid Id { get; set; } + + [ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string MediaSourceId { get; set; } + + [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string DeviceId { get; set; } + + [ApiMember(Name = "Container", Description = "Container", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string Container { get; set; } + + /// + /// Gets or sets the audio codec. + /// + /// The audio codec. + [ApiMember(Name = "AudioCodec", Description = "Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string AudioCodec { get; set; } + [ApiMember(Name = "EnableAutoStreamCopy", Description = "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool EnableAutoStreamCopy { get; set; } @@ -180,7 +203,7 @@ namespace MediaBrowser.Controller.MediaEncoding public bool RequireNonAnamorphic { get; set; } public int? TranscodingMaxAudioChannels { get; set; } public int? CpuCoreLimit { get; set; } - public string OutputContainer { get; set; } + public string LiveStreamId { get; set; } public bool EnableMpegtsM2TsMode { get; set; } @@ -244,11 +267,5 @@ namespace MediaBrowser.Controller.MediaEncoding Context = EncodingContext.Streaming; StreamOptions = new Dictionary(StringComparer.OrdinalIgnoreCase); } - - public string TempDirectory { get; set; } - public string DeviceId { get; set; } - public Guid Id { get; set; } - public string Container { get; set; } - public DeviceProfile DeviceProfile { get; set; } } } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs index df78d117a..d4040cd31 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs @@ -15,7 +15,6 @@ namespace MediaBrowser.MediaEncoding.Encoder public bool IsCancelled { get; internal set; } public Stream LogFileStream { get; set; } - public IProgress Progress { get; set; } public TaskCompletionSource TaskCompletionSource; public EncodingJobOptions Options @@ -24,34 +23,22 @@ namespace MediaBrowser.MediaEncoding.Encoder set => BaseRequest = value; } - public string Id { get; set; } + public Guid Id { get; set; } - public string MimeType { get; set; } public bool EstimateContentLength { get; set; } public TranscodeSeekInfo TranscodeSeekInfo { get; set; } - public long? EncodingDurationTicks { get; set; } public string ItemType { get; set; } - public string GetMimeType(string outputPath) - { - if (!string.IsNullOrEmpty(MimeType)) - { - return MimeType; - } - - return MimeTypes.GetMimeType(outputPath); - } - private readonly ILogger _logger; private readonly IMediaSourceManager _mediaSourceManager; public EncodingJob(ILogger logger, IMediaSourceManager mediaSourceManager) : - base(logger, mediaSourceManager, TranscodingJobType.Progressive) + base(TranscodingJobType.Progressive) { _logger = logger; _mediaSourceManager = mediaSourceManager; - Id = Guid.NewGuid().ToString("N"); + Id = Guid.NewGuid(); _logger = logger; TaskCompletionSource = new TaskCompletionSource(); @@ -61,6 +48,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { DisposeLiveStream(); DisposeLogStream(); + DisposeIsoMount(); } private void DisposeLogStream() @@ -95,49 +83,21 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - public string OutputFilePath { get; set; } - public string ActualOutputVideoCodec + private void DisposeIsoMount() { - get + if (IsoMount != null) { - var codec = OutputVideoCodec; - - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) + try { - var stream = VideoStream; - - if (stream != null) - { - return stream.Codec; - } - - return null; + IsoMount.Dispose(); + } + catch (Exception ex) + { + _logger.LogError("Error disposing iso mount", ex); } - return codec; - } - } - - public string ActualOutputAudioCodec - { - get - { - var codec = OutputAudioCodec; - - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) - { - var stream = AudioStream; - - if (stream != null) - { - return stream.Codec; - } - - return null; - } - - return codec; + IsoMount = null; } } From 060eb98cc532bedae80c92bf50ed6c369023cfad Mon Sep 17 00:00:00 2001 From: Mathieu Velten Date: Sun, 16 Dec 2018 17:36:08 +0100 Subject: [PATCH 2/3] Fix encoding profile handling for vaapi --- .../MediaEncoding/EncodingHelper.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index f7b9be806..e6899857f 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -760,14 +760,18 @@ namespace MediaBrowser.Controller.MediaEncoding var request = state.BaseRequest; var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault(); - if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + + // vaapi does not support Baseline profile, force Constrained Baseline in this case, + // which is compatible (and ugly) + if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && + profile != null && profile.ToLower().Contains("baseline")) { - param += " -profile:v 578"; + profile = "constrained_baseline"; } - else if (!string.IsNullOrEmpty(profile)) + + if (!string.IsNullOrEmpty(profile)) { if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) { // not supported by h264_omx From ae73f7b3e370f94d6e667a153be363ab89a24f78 Mon Sep 17 00:00:00 2001 From: Mathieu Velten Date: Sun, 16 Dec 2018 17:35:06 +0100 Subject: [PATCH 3/3] Fix transcoding bitrate --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index e6899857f..e086f9d33 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1093,11 +1093,6 @@ namespace MediaBrowser.Controller.MediaEncoding if (videoStream != null) { - if (bitrate.HasValue && videoStream.BitRate.HasValue) - { - bitrate = Math.Min(videoStream.BitRate.Value, bitrate.Value); - } - if (bitrate.HasValue) { var inputVideoCodec = videoStream.Codec;