浏览代码

Add AV1 support in HLS streaming

Signed-off-by: nyanmisaka <nst799610810@gmail.com>
nyanmisaka 1 年之前
父节点
当前提交
0df6fd9cf2

+ 36 - 6
Jellyfin.Api/Helpers/DynamicHlsHelper.cs

@@ -210,6 +210,7 @@ public class DynamicHlsHelper
 
             // Provide SDR HEVC entrance for backward compatibility.
             if (encodingOptions.AllowHevcEncoding
+                && !encodingOptions.AllowAv1Encoding
                 && EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
                 && !string.IsNullOrEmpty(state.VideoStream.VideoRange)
                 && string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
@@ -252,7 +253,9 @@ public class DynamicHlsHelper
             // Provide Level 5.0 entrance for backward compatibility.
             // e.g. Apple A10 chips refuse the master playlist containing SDR HEVC Main Level 5.1 video,
             // but in fact it is capable of playing videos up to Level 6.1.
-            if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
+            if (encodingOptions.AllowHevcEncoding
+                && !encodingOptions.AllowAv1Encoding
+                && EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
                 && state.VideoStream.Level.HasValue
                 && state.VideoStream.Level > 150
                 && !string.IsNullOrEmpty(state.VideoStream.VideoRange)
@@ -555,6 +558,12 @@ public class DynamicHlsHelper
                 levelString = state.GetRequestedLevel("h265") ?? state.GetRequestedLevel("hevc") ?? "120";
                 levelString = EncodingHelper.NormalizeTranscodingLevel(state, levelString);
             }
+
+            if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase))
+            {
+                levelString = state.GetRequestedLevel("av1") ?? "19";
+                levelString = EncodingHelper.NormalizeTranscodingLevel(state, levelString);
+            }
         }
 
         if (int.TryParse(levelString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedLevel))
@@ -566,11 +575,11 @@ public class DynamicHlsHelper
     }
 
     /// <summary>
-    /// Get the H.26X profile of the output video stream.
+    /// Get the profile of the output video stream.
     /// </summary>
     /// <param name="state">StreamState of the current stream.</param>
     /// <param name="codec">Video codec.</param>
-    /// <returns>H.26X profile of the output video stream.</returns>
+    /// <returns>Profile of the output video stream.</returns>
     private string GetOutputVideoCodecProfile(StreamState state, string codec)
     {
         string profileString = string.Empty;
@@ -592,6 +601,11 @@ public class DynamicHlsHelper
             {
                 profileString ??= "main";
             }
+
+            if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase))
+            {
+                profileString ??= "main";
+            }
         }
 
         return profileString;
@@ -658,9 +672,9 @@ public class DynamicHlsHelper
     {
         if (level == 0)
         {
-            // This is 0 when there's no requested H.26X level in the device profile
-            // and the source is not encoded in H.26X
-            _logger.LogError("Got invalid H.26X level when building CODECS field for HLS master playlist");
+            // This is 0 when there's no requested level in the device profile
+            // and the source is not encoded in H.26X or AV1
+            _logger.LogError("Got invalid level when building CODECS field for HLS master playlist");
             return string.Empty;
         }
 
@@ -677,6 +691,22 @@ public class DynamicHlsHelper
             return HlsCodecStringHelpers.GetH265String(profile, level);
         }
 
+        if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
+        {
+            string profile = GetOutputVideoCodecProfile(state, "av1");
+
+            // Currently we only transcode to 8 bits AV1
+            int bitDepth = 8;
+            if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
+                && state.VideoStream != null
+                && state.VideoStream.BitDepth.HasValue)
+            {
+                bitDepth = state.VideoStream.BitDepth.Value;
+            }
+
+            return HlsCodecStringHelpers.GetAv1String(profile, level, false, bitDepth);
+        }
+
         return string.Empty;
     }
 

+ 56 - 0
Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs

@@ -179,4 +179,60 @@ public static class HlsCodecStringHelpers
 
         return result.ToString();
     }
+
+    /// <summary>
+    /// Gets a AV1 codec string.
+    /// </summary>
+    /// <param name="profile">AV1 profile.</param>
+    /// <param name="level">AV1 level.</param>
+    /// <param name="tierFlag">AV1 tier flag.</param>
+    /// <param name="bitDepth">AV1 bit depth.</param>
+    /// <returns>AV1 string.</returns>
+    public static string GetAv1String(string? profile, int level, bool tierFlag, int bitDepth)
+    {
+        // https://aomedia.org/av1/specification/annex-a/
+        // FORMAT: [codecTag].[profile].[level][tier].[bitDepth]
+        StringBuilder result = new StringBuilder("av01", 13);
+
+        if (string.Equals(profile, "Main", StringComparison.OrdinalIgnoreCase))
+        {
+            result.Append(".0");
+        }
+        else if (string.Equals(profile, "High", StringComparison.OrdinalIgnoreCase))
+        {
+            result.Append(".1");
+        }
+        else if (string.Equals(profile, "Professional", StringComparison.OrdinalIgnoreCase))
+        {
+            result.Append(".2");
+        }
+        else
+        {
+            // Default to Main
+            result.Append(".0");
+        }
+
+        if (level <= 0
+            || level > 31)
+        {
+            // Default to the maximum defined level 6.3
+            level = 19;
+        }
+
+        if (bitDepth != 8
+            && bitDepth != 10
+            && bitDepth != 12)
+        {
+            // Default to 8 bits
+            bitDepth = 8;
+        }
+
+        result.Append("." + level)
+            .Append(tierFlag ? "H" : "M");
+
+        string bitDepthD2 = bitDepth.ToString("D2", CultureInfo.InvariantCulture);
+        result.Append("." + bitDepthD2);
+
+        return result.ToString();
+    }
 }

+ 7 - 2
Jellyfin.Api/Helpers/StreamingHelpers.cs

@@ -430,12 +430,17 @@ public static class StreamingHelpers
         {
             var videoCodec = state.Request.VideoCodec;
 
-            if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) ||
-                string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
             {
                 return ".ts";
             }
 
+            if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
+                || string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase))
+            {
+                return ".mp4";
+            }
+
             if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
             {
                 return ".ogv";

+ 1 - 1
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -23,7 +23,7 @@ namespace MediaBrowser.Model.Dlna
 
         private readonly ILogger _logger;
         private readonly ITranscoderSupport _transcoderSupport;
-        private static readonly string[] _supportedHlsVideoCodecs = new string[] { "h264", "hevc" };
+        private static readonly string[] _supportedHlsVideoCodecs = new string[] { "h264", "hevc", "av1" };
         private static readonly string[] _supportedHlsAudioCodecsTs = new string[] { "aac", "ac3", "eac3", "mp3" };
         private static readonly string[] _supportedHlsAudioCodecsMp4 = new string[] { "aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd" };