diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
index 4486954c62..b66aa69350 100644
--- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
+++ b/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
}
///
- /// Get the H.26X profile of the output video stream.
+ /// Get the profile of the output video stream.
///
/// StreamState of the current stream.
/// Video codec.
- /// H.26X profile of the output video stream.
+ /// Profile of the output video stream.
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;
}
diff --git a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
index 995488397e..ad1fce0f14 100644
--- a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
+++ b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
@@ -179,4 +179,60 @@ public static class HlsCodecStringHelpers
return result.ToString();
}
+
+ ///
+ /// Gets a AV1 codec string.
+ ///
+ /// AV1 profile.
+ /// AV1 level.
+ /// AV1 tier flag.
+ /// AV1 bit depth.
+ /// AV1 string.
+ 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();
+ }
}
diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs
index 9c91dcc6fe..782cd65685 100644
--- a/Jellyfin.Api/Helpers/StreamingHelpers.cs
+++ b/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";
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 0a955e917f..b82c9cf26b 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/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" };