mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-04-24 22:17:25 -04:00
Add AV1 support in HLS streaming
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
This commit is contained in:
parent
4972fbf2a3
commit
0df6fd9cf2
4 changed files with 100 additions and 9 deletions
|
@ -210,6 +210,7 @@ public class DynamicHlsHelper
|
||||||
|
|
||||||
// Provide SDR HEVC entrance for backward compatibility.
|
// Provide SDR HEVC entrance for backward compatibility.
|
||||||
if (encodingOptions.AllowHevcEncoding
|
if (encodingOptions.AllowHevcEncoding
|
||||||
|
&& !encodingOptions.AllowAv1Encoding
|
||||||
&& EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
|
&& EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
|
||||||
&& !string.IsNullOrEmpty(state.VideoStream.VideoRange)
|
&& !string.IsNullOrEmpty(state.VideoStream.VideoRange)
|
||||||
&& string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
|
&& string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
|
||||||
|
@ -252,7 +253,9 @@ public class DynamicHlsHelper
|
||||||
// Provide Level 5.0 entrance for backward compatibility.
|
// 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,
|
// 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.
|
// 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.HasValue
|
||||||
&& state.VideoStream.Level > 150
|
&& state.VideoStream.Level > 150
|
||||||
&& !string.IsNullOrEmpty(state.VideoStream.VideoRange)
|
&& !string.IsNullOrEmpty(state.VideoStream.VideoRange)
|
||||||
|
@ -555,6 +558,12 @@ public class DynamicHlsHelper
|
||||||
levelString = state.GetRequestedLevel("h265") ?? state.GetRequestedLevel("hevc") ?? "120";
|
levelString = state.GetRequestedLevel("h265") ?? state.GetRequestedLevel("hevc") ?? "120";
|
||||||
levelString = EncodingHelper.NormalizeTranscodingLevel(state, levelString);
|
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))
|
if (int.TryParse(levelString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedLevel))
|
||||||
|
@ -566,11 +575,11 @@ public class DynamicHlsHelper
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the H.26X profile of the output video stream.
|
/// Get the profile of the output video stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">StreamState of the current stream.</param>
|
/// <param name="state">StreamState of the current stream.</param>
|
||||||
/// <param name="codec">Video codec.</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)
|
private string GetOutputVideoCodecProfile(StreamState state, string codec)
|
||||||
{
|
{
|
||||||
string profileString = string.Empty;
|
string profileString = string.Empty;
|
||||||
|
@ -592,6 +601,11 @@ public class DynamicHlsHelper
|
||||||
{
|
{
|
||||||
profileString ??= "main";
|
profileString ??= "main";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
profileString ??= "main";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return profileString;
|
return profileString;
|
||||||
|
@ -658,9 +672,9 @@ public class DynamicHlsHelper
|
||||||
{
|
{
|
||||||
if (level == 0)
|
if (level == 0)
|
||||||
{
|
{
|
||||||
// This is 0 when there's no requested H.26X level in the device profile
|
// This is 0 when there's no requested level in the device profile
|
||||||
// and the source is not encoded in H.26X
|
// and the source is not encoded in H.26X or AV1
|
||||||
_logger.LogError("Got invalid H.26X level when building CODECS field for HLS master playlist");
|
_logger.LogError("Got invalid level when building CODECS field for HLS master playlist");
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -677,6 +691,22 @@ public class DynamicHlsHelper
|
||||||
return HlsCodecStringHelpers.GetH265String(profile, level);
|
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;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -179,4 +179,60 @@ public static class HlsCodecStringHelpers
|
||||||
|
|
||||||
return result.ToString();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -430,12 +430,17 @@ public static class StreamingHelpers
|
||||||
{
|
{
|
||||||
var videoCodec = state.Request.VideoCodec;
|
var videoCodec = state.Request.VideoCodec;
|
||||||
|
|
||||||
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) ||
|
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
|
||||||
string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
return ".ts";
|
return ".ts";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return ".mp4";
|
||||||
|
}
|
||||||
|
|
||||||
if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return ".ogv";
|
return ".ogv";
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly ITranscoderSupport _transcoderSupport;
|
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[] _supportedHlsAudioCodecsTs = new string[] { "aac", "ac3", "eac3", "mp3" };
|
||||||
private static readonly string[] _supportedHlsAudioCodecsMp4 = new string[] { "aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd" };
|
private static readonly string[] _supportedHlsAudioCodecsMp4 = new string[] { "aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd" };
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue