Преглед изворни кода

Fix #1432. Add support for encoding with libx265 and hevc_nvenc.

Frank пре 6 година
родитељ
комит
3ba709fcc3

+ 1 - 0
CONTRIBUTORS.md

@@ -24,6 +24,7 @@
  - [Lynxy](https://github.com/Lynxy)
  - [fasheng](https://github.com/fasheng)
  - [ploughpuff](https://github.com/ploughpuff) 
+ - [fhriley](https://github.com/fhriley)
 
 # Emby Contributors
 

+ 1 - 1
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -154,7 +154,7 @@ namespace MediaBrowser.Api.Playback
 
         protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
 
-        protected virtual string GetDefaultH264Preset()
+        protected virtual string GetDefaultEncoderPreset()
         {
             return "superfast";
         }

+ 1 - 1
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -303,7 +303,7 @@ namespace MediaBrowser.Api.Playback.Hls
             return args;
         }
 
-        protected override string GetDefaultH264Preset()
+        protected override string GetDefaultEncoderPreset()
         {
             return "veryfast";
         }

+ 7 - 3
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -895,9 +895,13 @@ namespace MediaBrowser.Api.Playback.Hls
             // See if we can save come cpu cycles by avoiding encoding
             if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
             {
-                if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
+                if (state.VideoStream != null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
                 {
-                    args += " -bsf:v h264_mp4toannexb";
+                    string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream);
+                    if (!string.IsNullOrEmpty(bitStreamArgs))
+                    {
+                        args += " " + bitStreamArgs;
+                    }
                 }
 
                 //args += " -flags -global_header";
@@ -909,7 +913,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
                 var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
 
-                args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
+                args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultEncoderPreset()) + keyFrameArg;
 
                 //args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
 

+ 7 - 3
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -91,10 +91,14 @@ namespace MediaBrowser.Api.Playback.Hls
             if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
             {
                 // if h264_mp4toannexb is ever added, do not use it for live tv
-                if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) &&
+                if (state.VideoStream != null &&
                     !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
                 {
-                    args += " -bsf:v h264_mp4toannexb";
+                    string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream);
+                    if (!string.IsNullOrEmpty(bitStreamArgs))
+                    {
+                        args += " " + bitStreamArgs;
+                    }
                 }
             }
             else
@@ -104,7 +108,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
                 var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
 
-                args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
+                args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultEncoderPreset()) + keyFrameArg;
 
                 // Add resolution params, if specified
                 if (!hasGraphicalSubs)

+ 2 - 1
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -77,7 +77,8 @@ namespace MediaBrowser.Api.Playback.Progressive
             {
                 var videoCodec = state.VideoRequest.VideoCodec;
 
-                if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
+                if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) ||
+                    string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase))
                 {
                     return ".ts";
                 }

+ 1 - 1
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -120,7 +120,7 @@ namespace MediaBrowser.Api.Playback.Progressive
 
         protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
         {
-            return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, GetDefaultH264Preset());
+            return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, GetDefaultEncoderPreset());
         }
     }
 }

+ 98 - 41
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -34,8 +34,16 @@ namespace MediaBrowser.Controller.MediaEncoding
 
         public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
         {
-            var defaultEncoder = "libx264";
+            return GetH264OrH265Encoder("libx264", "h264", state, encodingOptions);
+        }
 
+        public string GetH265Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
+        {
+            return GetH264OrH265Encoder("libx265", "hevc", state, encodingOptions);
+        }
+
+        private string GetH264OrH265Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, 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
             // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
@@ -45,14 +53,14 @@ namespace MediaBrowser.Controller.MediaEncoding
 
                 var codecMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
                 {
-                    {"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"}
+                    {"qsv",                  hwEncoder + "_qsv"},
+                    {hwEncoder + "_qsv",     hwEncoder + "_qsv"},
+                    {"nvenc",                hwEncoder + "_nvenc"},
+                    {"amf",                  hwEncoder + "_amf"},
+                    {"omx",                  hwEncoder + "_omx"},
+                    {hwEncoder + "_v4l2m2m", hwEncoder + "_v4l2m2m"},
+                    {"mediacodec",           hwEncoder + "_mediacodec"},
+                    {"vaapi",                hwEncoder + "_vaapi"}
                 };
 
                 if (!string.IsNullOrEmpty(hwType)
@@ -119,6 +127,11 @@ namespace MediaBrowser.Controller.MediaEncoding
 
             if (!string.IsNullOrEmpty(codec))
             {
+                if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) ||
+                    string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
+                {
+                    return GetH265Encoder(state, encodingOptions);
+                }
                 if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
                 {
                     return GetH264Encoder(state, encodingOptions);
@@ -476,6 +489,30 @@ namespace MediaBrowser.Controller.MediaEncoding
                    codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1;
         }
 
+        public bool IsH265(MediaStream stream)
+        {
+            var codec = stream.Codec ?? string.Empty;
+
+            return codec.IndexOf("265", StringComparison.OrdinalIgnoreCase) != -1 ||
+                   codec.IndexOf("hevc", StringComparison.OrdinalIgnoreCase) != -1;
+        }
+
+        public string GetBitStreamArgs(MediaStream stream)
+        {
+            if (IsH264(stream))
+            {
+                return "-bsf:v h264_mp4toannexb";
+            }
+            else if (IsH265(stream))
+            {
+                return "-bsf:v hevc_mp4toannexb";
+            }
+            else
+            {
+                return null;
+            }
+        }
+
         public string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
         {
             var bitrate = state.OutputVideoBitrate;
@@ -494,7 +531,8 @@ namespace MediaBrowser.Controller.MediaEncoding
                     return string.Format(" -b:v {0}", bitrate.Value.ToString(_usCulture));
                 }
 
-                if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
+                if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase) ||
+                    string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
                 {
                     // h264
                     return string.Format(" -maxrate {0} -bufsize {1}",
@@ -515,8 +553,10 @@ namespace MediaBrowser.Controller.MediaEncoding
         {
             // Clients may direct play higher than level 41, but there's no reason to transcode higher
             if (double.TryParse(level, NumberStyles.Any, _usCulture, out double requestLevel)
-                && string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
-                && requestLevel > 41)
+                && requestLevel > 41
+                && (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
+                    || string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase)
+                    || string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)))
             {
                 return "41";
             }
@@ -616,49 +656,53 @@ namespace MediaBrowser.Controller.MediaEncoding
         /// <summary>
         /// Gets the video bitrate to specify on the command line
         /// </summary>
-        public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultH264Preset)
+        public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultPreset)
         {
             var param = string.Empty;
 
             var isVc1 = state.VideoStream != null &&
                 string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
+            var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
 
-            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
             {
-                if (!string.IsNullOrEmpty(encodingOptions.H264Preset))
+                if (!string.IsNullOrEmpty(encodingOptions.EncoderPreset))
                 {
-                    param += "-preset " + encodingOptions.H264Preset;
+                    param += "-preset " + encodingOptions.EncoderPreset;
                 }
                 else
                 {
-                    param += "-preset " + defaultH264Preset;
+                    param += "-preset " + defaultPreset;
                 }
 
-                if (encodingOptions.H264Crf >= 0 && encodingOptions.H264Crf <= 51)
+                int encodeCrf = encodingOptions.H264Crf;
+                if (isLibX265)
                 {
-                    param += " -crf " + encodingOptions.H264Crf.ToString(CultureInfo.InvariantCulture);
+                    encodeCrf = encodingOptions.H265Crf;
+                }
+                if (encodeCrf >= 0 && encodeCrf <= 51)
+                {
+                    param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture);
                 }
                 else
                 {
-                    param += " -crf 23";
+                    string defaultCrf = "23";
+                    if (isLibX265)
+                    {
+                        defaultCrf = "28";
+                    }
+                    param += " -crf " + defaultCrf;
                 }
             }
 
-            else if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
-            {
-                param += "-preset fast";
-
-                param += " -crf 28";
-            }
-
             // h264 (h264_qsv)
             else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
             {
                 string[] valid_h264_qsv = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" };
 
-                if (valid_h264_qsv.Contains(encodingOptions.H264Preset, StringComparer.OrdinalIgnoreCase))
+                if (valid_h264_qsv.Contains(encodingOptions.EncoderPreset, StringComparer.OrdinalIgnoreCase))
                 {
-                    param += "-preset " + encodingOptions.H264Preset;
+                    param += "-preset " + encodingOptions.EncoderPreset;
                 }
                 else
                 {
@@ -670,9 +714,10 @@ namespace MediaBrowser.Controller.MediaEncoding
             }
 
             // h264 (h264_nvenc)
-            else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+            else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) ||
+                     string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase))
             {
-                switch (encodingOptions.H264Preset)
+                switch (encodingOptions.EncoderPreset)
                 {
                     case "veryslow":
 
@@ -786,7 +831,8 @@ namespace MediaBrowser.Controller.MediaEncoding
                 // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
                 // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307
                 if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
-                    string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
+                    string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) ||
+                    string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
                 {
                     switch (level)
                     {
@@ -823,9 +869,10 @@ namespace MediaBrowser.Controller.MediaEncoding
                     }
                 }
                 // nvenc doesn't decode with param -level set ?!
-                else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+                else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) ||
+                         string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase))
                 {
-                    //param += "";
+                    // todo param += ""; 
                 }
                 else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase))
                 {
@@ -838,6 +885,11 @@ namespace MediaBrowser.Controller.MediaEncoding
                 param += " -x264opts:0 subme=0:me_range=4:rc_lookahead=10:me=dia:no_chroma_me:8x8dct=0:partitions=none";
             }
 
+            if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
+            {
+                // todo
+            }
+
             if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
                 !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
                 !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) &&
@@ -1715,7 +1767,8 @@ namespace MediaBrowser.Controller.MediaEncoding
 
             var videoStream = state.VideoStream;
 
-            if (state.DeInterlace("h264", true) && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+            if ((state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true)) &&
+                !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
             {
                 var inputFramerate = videoStream == null ? null : videoStream.RealFrameRate;
 
@@ -2376,7 +2429,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             return args;
         }
 
-        public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string outputPath, string defaultH264Preset)
+        public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string outputPath, string defaultPreset)
         {
             // Get the output codec name
             var videoCodec = GetVideoEncoder(state, encodingOptions);
@@ -2400,7 +2453,7 @@ namespace MediaBrowser.Controller.MediaEncoding
                 GetInputArgument(state, encodingOptions),
                 keyFrame,
                 GetMapArgs(state),
-                GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultH264Preset),
+                GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset),
                 threads,
                 GetProgressiveVideoAudioArguments(state, encodingOptions),
                 GetSubtitleEmbedArguments(state),
@@ -2425,7 +2478,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             return string.Empty;
         }
 
-        public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoCodec, string defaultH264Preset)
+        public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoCodec, string defaultPreset)
         {
             var args = "-codec:v:0 " + videoCodec;
 
@@ -2436,11 +2489,15 @@ namespace MediaBrowser.Controller.MediaEncoding
 
             if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
             {
-                if (state.VideoStream != null && IsH264(state.VideoStream) &&
+                if (state.VideoStream != null &&
                     string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) &&
                     !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
                 {
-                    args += " -bsf:v h264_mp4toannexb";
+                    string bitStreamArgs = GetBitStreamArgs(state.VideoStream);
+                    if (!string.IsNullOrEmpty(bitStreamArgs))
+                    {
+                        args += " " + bitStreamArgs;
+                    }
                 }
 
                 if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
@@ -2486,7 +2543,7 @@ namespace MediaBrowser.Controller.MediaEncoding
                     args += GetGraphicalSubtitleParam(state, encodingOptions, videoCodec);
                 }
 
-                var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultH264Preset);
+                var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset);
 
                 if (!string.IsNullOrEmpty(qualityParam))
                 {

+ 3 - 3
MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs

@@ -118,14 +118,14 @@ namespace MediaBrowser.Controller.MediaEncoding
         /// Gets or sets the profile.
         /// </summary>
         /// <value>The profile.</value>
-        [ApiMember(Name = "Profile", Description = "Optional. Specify a specific h264 profile, e.g. main, baseline, high.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        [ApiMember(Name = "Profile", Description = "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string Profile { get; set; }
 
         /// <summary>
         /// Gets or sets the level.
         /// </summary>
         /// <value>The level.</value>
-        [ApiMember(Name = "Level", Description = "Optional. Specify a level for the h264 profile, e.g. 3, 3.1.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        [ApiMember(Name = "Level", Description = "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string Level { get; set; }
 
         /// <summary>
@@ -212,7 +212,7 @@ namespace MediaBrowser.Controller.MediaEncoding
         /// Gets or sets the video codec.
         /// </summary>
         /// <value>The video codec.</value>
-        [ApiMember(Name = "VideoCodec", Description = "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h264, mpeg4, theora, vpx, wmv.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        [ApiMember(Name = "VideoCodec", Description = "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string VideoCodec { get; set; }
 
         public string SubtitleCodec { get; set; }

+ 3 - 1
MediaBrowser.Model/Configuration/EncodingOptions.cs

@@ -18,7 +18,8 @@ namespace MediaBrowser.Model.Configuration
         public string EncoderAppPathDisplay { get; set; }
         public string VaapiDevice { get; set; }
         public int H264Crf { get; set; }
-        public string H264Preset { get; set; }
+        public int H265Crf { get; set; }
+        public string EncoderPreset { get; set; }
         public string DeinterlaceMethod { get; set; }
         public bool EnableHardwareEncoding { get; set; }
         public bool EnableSubtitleExtraction { get; set; }
@@ -34,6 +35,7 @@ namespace MediaBrowser.Model.Configuration
             // This is a DRM device that is almost guaranteed to be there on every intel platform, plus it's the default one in ffmpeg if you don't specify anything
             VaapiDevice = "/dev/dri/renderD128";
             H264Crf = 23;
+            H265Crf = 28;
             EnableHardwareEncoding = true;
             EnableSubtitleExtraction = true;
             HardwareDecodingCodecs = new string[] { "h264", "vc1" };