add initial support for HEVC over FMP4-HLS
This commit is contained in:
parent
8c5e0ddae0
commit
85965741f5
|
@ -1136,11 +1136,19 @@ namespace Jellyfin.Api.Controllers
|
|||
|
||||
var segmentLengths = GetSegmentLengths(state);
|
||||
|
||||
var segmentContainer = state.Request.SegmentContainer ?? "ts";
|
||||
|
||||
// http://ffmpeg.org/ffmpeg-all.html#toc-hls-2
|
||||
var isHlsInFmp4 = string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase);
|
||||
var hlsVersion = isHlsInFmp4 ? "7" : "3";
|
||||
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.AppendLine("#EXTM3U")
|
||||
.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD")
|
||||
.AppendLine("#EXT-X-VERSION:3")
|
||||
.Append("#EXT-X-VERSION:")
|
||||
.Append(hlsVersion)
|
||||
.AppendLine()
|
||||
.Append("#EXT-X-TARGETDURATION:")
|
||||
.Append(Math.Ceiling(segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength))
|
||||
.AppendLine()
|
||||
|
@ -1150,6 +1158,18 @@ namespace Jellyfin.Api.Controllers
|
|||
var segmentExtension = GetSegmentFileExtension(streamingRequest.SegmentContainer);
|
||||
var queryString = Request.QueryString;
|
||||
|
||||
if (isHlsInFmp4)
|
||||
{
|
||||
builder.Append("#EXT-X-MAP:URI=\"")
|
||||
.Append("hls1/")
|
||||
.Append(name)
|
||||
.Append("/-1")
|
||||
.Append(segmentExtension)
|
||||
.Append(queryString)
|
||||
.Append('"')
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
foreach (var length in segmentLengths)
|
||||
{
|
||||
builder.Append("#EXTINF:")
|
||||
|
@ -1232,7 +1252,13 @@ namespace Jellyfin.Api.Controllers
|
|||
var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
|
||||
var segmentGapRequiringTranscodingChange = 24 / state.SegmentLength;
|
||||
|
||||
if (currentTranscodingIndex == null)
|
||||
if (segmentId == -1)
|
||||
{
|
||||
_logger.LogDebug("Starting transcoding because fmp4 header file is being requested");
|
||||
startTranscoding = true;
|
||||
segmentId = 0;
|
||||
}
|
||||
else if (currentTranscodingIndex == null)
|
||||
{
|
||||
_logger.LogDebug("Starting transcoding because currentTranscodingIndex=null");
|
||||
startTranscoding = true;
|
||||
|
@ -1347,13 +1373,24 @@ namespace Jellyfin.Api.Controllers
|
|||
|
||||
var mapArgs = state.IsOutputVideo ? _encodingHelper.GetMapArgs(state) : string.Empty;
|
||||
|
||||
var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request.SegmentContainer);
|
||||
var outputPrefix = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath));
|
||||
var outputExtension = GetSegmentFileExtension(state.Request.SegmentContainer);
|
||||
var outputTsArg = outputPrefix + "%d" + outputExtension;
|
||||
|
||||
var segmentFormat = GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.');
|
||||
if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
segmentFormat = "mpegts";
|
||||
}
|
||||
else if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var outputFmp4HeaderArg = " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"";
|
||||
segmentFormat = "fmp4" + outputFmp4HeaderArg;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError("Invalid HLS segment container: " + segmentFormat);
|
||||
}
|
||||
|
||||
var maxMuxingQueueSize = encodingOptions.MaxMuxingQueueSize > 128
|
||||
? encodingOptions.MaxMuxingQueueSize.ToString(CultureInfo.InvariantCulture)
|
||||
|
@ -1384,7 +1421,7 @@ namespace Jellyfin.Api.Controllers
|
|||
{
|
||||
if (EncodingHelper.IsCopyCodec(audioCodec))
|
||||
{
|
||||
return "-acodec copy";
|
||||
return "-acodec copy -strict -2";
|
||||
}
|
||||
|
||||
var audioTranscodeParams = new List<string>();
|
||||
|
@ -1416,10 +1453,10 @@ namespace Jellyfin.Api.Controllers
|
|||
|
||||
if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec))
|
||||
{
|
||||
return "-codec:a:0 copy -copypriorss:a:0 0";
|
||||
return "-codec:a:0 copy -strict -2 -copypriorss:a:0 0";
|
||||
}
|
||||
|
||||
return "-codec:a:0 copy";
|
||||
return "-codec:a:0 copy -strict -2";
|
||||
}
|
||||
|
||||
var args = "-codec:a:0 " + audioCodec;
|
||||
|
@ -1459,6 +1496,15 @@ namespace Jellyfin.Api.Controllers
|
|||
|
||||
var args = "-codec:v:0 " + codec;
|
||||
|
||||
// Prefer hvc1 to hev1
|
||||
if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
args += " -tag:v:0 hvc1";
|
||||
}
|
||||
|
||||
// if (state.EnableMpegtsM2TsMode)
|
||||
// {
|
||||
// args += " -mpegts_m2ts_mode 1";
|
||||
|
@ -1505,18 +1551,32 @@ namespace Jellyfin.Api.Controllers
|
|||
|
||||
args += " " + _encodingHelper.GetVideoQualityParam(state, codec, encodingOptions, "veryfast");
|
||||
|
||||
// Unable to force key frames using these hw encoders, set key frames by GOP
|
||||
// Unable to force key frames using these encoders, set key frames by GOP
|
||||
if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase))
|
||||
|| string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "hevc_amf", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
args += " " + gopArg;
|
||||
}
|
||||
else if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
args += " " + keyFrameArg;
|
||||
}
|
||||
else
|
||||
{
|
||||
args += " " + keyFrameArg + gopArg;
|
||||
}
|
||||
|
||||
// Currenly b-frames in libx265 breaks the FMP4-HLS playback on iOS, disable it for now
|
||||
if (string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
args += " -bf 0";
|
||||
}
|
||||
|
||||
// args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
|
||||
|
||||
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
||||
|
|
|
@ -191,8 +191,11 @@ namespace Jellyfin.Api.Controllers
|
|||
if (!isStatic && string.Equals(mediaSource.TranscodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// hls segment container can only be mpegts or fmp4 per ffmpeg documentation
|
||||
// ffmpeg option -> file extension
|
||||
// mpegts -> ts
|
||||
// fmp4 -> mp4
|
||||
// TODO: remove this when we switch back to the segment muxer
|
||||
var supportedHlsContainers = new[] { "mpegts", "fmp4" };
|
||||
var supportedHlsContainers = new[] { "ts", "mp4" };
|
||||
|
||||
var dynamicHlsRequestDto = new HlsAudioRequestDto
|
||||
{
|
||||
|
@ -201,7 +204,7 @@ namespace Jellyfin.Api.Controllers
|
|||
Static = isStatic,
|
||||
PlaySessionId = info.PlaySessionId,
|
||||
// fallback to mpegts if device reports some weird value unsupported by hls
|
||||
SegmentContainer = Array.Exists(supportedHlsContainers, element => element == transcodingContainer) ? transcodingContainer : "mpegts",
|
||||
SegmentContainer = Array.Exists(supportedHlsContainers, element => element == transcodingContainer) ? transcodingContainer : "ts",
|
||||
MediaSourceId = mediaSourceId,
|
||||
DeviceId = deviceId,
|
||||
AudioCodec = audioCodec,
|
||||
|
|
|
@ -202,7 +202,61 @@ namespace Jellyfin.Api.Helpers
|
|||
AddSubtitles(state, subtitleStreams, builder, _httpContextAccessor.HttpContext.User);
|
||||
}
|
||||
|
||||
AppendPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup);
|
||||
var basicPlaylist = AppendPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup);
|
||||
|
||||
if (state.VideoStream != null && state.VideoRequest != null)
|
||||
{
|
||||
// Provide SDR HEVC entrance for backward compatibility
|
||||
if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
|
||||
&& !string.IsNullOrEmpty(state.VideoStream.VideoRange)
|
||||
&& string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var requestedVideoProfiles = state.GetRequestedProfiles("hevc");
|
||||
if (requestedVideoProfiles != null && requestedVideoProfiles.Length > 0)
|
||||
{
|
||||
// Force HEVC Main Profile and disable video stream copy
|
||||
state.OutputVideoCodec = "hevc";
|
||||
var sdrVideoUrl = ReplaceProfile(playlistUrl, "hevc", string.Join(",", requestedVideoProfiles), "main");
|
||||
sdrVideoUrl += "&AllowVideoStreamCopy=false";
|
||||
|
||||
EncodingHelper encodingHelper = new EncodingHelper(_mediaEncoder, _fileSystem, _subtitleEncoder, _configuration);
|
||||
var sdrOutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec) ?? 0;
|
||||
var sdrOutputAudioBitrate = encodingHelper.GetAudioBitrateParam(state.VideoRequest.AudioBitRate, state.AudioStream) ?? 0;
|
||||
var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate;
|
||||
|
||||
AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup);
|
||||
|
||||
// Restore the video codec
|
||||
state.OutputVideoCodec = "copy";
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
&& state.VideoStream.Level.HasValue
|
||||
&& state.VideoStream.Level > 150
|
||||
&& !string.IsNullOrEmpty(state.VideoStream.VideoRange)
|
||||
&& string.Equals(state.VideoStream.VideoRange, "SDR", StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var playlistCodecsField = new StringBuilder();
|
||||
AppendPlaylistCodecsField(playlistCodecsField, state);
|
||||
|
||||
// Force the video level to 5.0
|
||||
var originalLevel = state.VideoStream.Level;
|
||||
state.VideoStream.Level = 150;
|
||||
var newPlaylistCodecsField = new StringBuilder();
|
||||
AppendPlaylistCodecsField(newPlaylistCodecsField, state);
|
||||
|
||||
// Restore the video level
|
||||
state.VideoStream.Level = originalLevel;
|
||||
var newPlaylist = ReplacePlaylistCodecsField(basicPlaylist, playlistCodecsField, newPlaylistCodecsField);
|
||||
builder.Append(newPlaylist);
|
||||
}
|
||||
}
|
||||
|
||||
if (EnableAdaptiveBitrateStreaming(state, isLiveStream, enableAdaptiveBitrateStreaming, _httpContextAccessor.HttpContext.GetNormalizedRemoteIp()))
|
||||
{
|
||||
|
@ -212,40 +266,77 @@ namespace Jellyfin.Api.Helpers
|
|||
var variation = GetBitrateVariation(totalBitrate);
|
||||
|
||||
var newBitrate = totalBitrate - variation;
|
||||
var variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
|
||||
var variantUrl = ReplaceVideoBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
|
||||
AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);
|
||||
|
||||
variation *= 2;
|
||||
newBitrate = totalBitrate - variation;
|
||||
variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
|
||||
variantUrl = ReplaceVideoBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
|
||||
AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);
|
||||
}
|
||||
|
||||
return new FileContentResult(Encoding.UTF8.GetBytes(builder.ToString()), MimeTypes.GetMimeType("playlist.m3u8"));
|
||||
}
|
||||
|
||||
private void AppendPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string? subtitleGroup)
|
||||
private StringBuilder AppendPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string? subtitleGroup)
|
||||
{
|
||||
builder.Append("#EXT-X-STREAM-INF:BANDWIDTH=")
|
||||
var playlistBuilder = new StringBuilder();
|
||||
playlistBuilder.Append("#EXT-X-STREAM-INF:BANDWIDTH=")
|
||||
.Append(bitrate.ToString(CultureInfo.InvariantCulture))
|
||||
.Append(",AVERAGE-BANDWIDTH=")
|
||||
.Append(bitrate.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
AppendPlaylistCodecsField(builder, state);
|
||||
AppendPlaylistVideoRangeField(playlistBuilder, state);
|
||||
|
||||
AppendPlaylistResolutionField(builder, state);
|
||||
AppendPlaylistCodecsField(playlistBuilder, state);
|
||||
|
||||
AppendPlaylistFramerateField(builder, state);
|
||||
AppendPlaylistResolutionField(playlistBuilder, state);
|
||||
|
||||
AppendPlaylistFramerateField(playlistBuilder, state);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(subtitleGroup))
|
||||
{
|
||||
builder.Append(",SUBTITLES=\"")
|
||||
playlistBuilder.Append(",SUBTITLES=\"")
|
||||
.Append(subtitleGroup)
|
||||
.Append('"');
|
||||
}
|
||||
|
||||
builder.Append(Environment.NewLine);
|
||||
builder.AppendLine(url);
|
||||
playlistBuilder.Append(Environment.NewLine);
|
||||
playlistBuilder.AppendLine(url);
|
||||
builder.Append(playlistBuilder);
|
||||
|
||||
return playlistBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a VIDEO-RANGE field containing the range of the output video stream.
|
||||
/// </summary>
|
||||
/// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/>
|
||||
/// <param name="builder">StringBuilder to append the field to.</param>
|
||||
/// <param name="state">StreamState of the current stream.</param>
|
||||
private void AppendPlaylistVideoRangeField(StringBuilder builder, StreamState state)
|
||||
{
|
||||
if (state.VideoStream != null && !string.IsNullOrEmpty(state.VideoStream.VideoRange))
|
||||
{
|
||||
var videoRange = state.VideoStream.VideoRange;
|
||||
if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
|
||||
{
|
||||
if (string.Equals(videoRange, "SDR", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
builder.Append(",VIDEO-RANGE=SDR");
|
||||
}
|
||||
|
||||
if (string.Equals(videoRange, "HDR", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
builder.Append(",VIDEO-RANGE=PQ");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Currently we only encode to SDR
|
||||
builder.Append(",VIDEO-RANGE=SDR");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -414,25 +505,68 @@ namespace Jellyfin.Api.Helpers
|
|||
/// <returns>H.26X level of the output video stream.</returns>
|
||||
private int? GetOutputVideoCodecLevel(StreamState state)
|
||||
{
|
||||
string? levelString;
|
||||
string levelString = string.Empty;
|
||||
if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
|
||||
&& state.VideoStream != null
|
||||
&& state.VideoStream.Level.HasValue)
|
||||
{
|
||||
levelString = state.VideoStream?.Level.ToString();
|
||||
levelString = state.VideoStream.Level.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
levelString = state.GetRequestedLevel(state.ActualOutputVideoCodec);
|
||||
if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
levelString = state.GetRequestedLevel(state.ActualOutputVideoCodec) ?? "41";
|
||||
levelString = EncodingHelper.NormalizeTranscodingLevel(state, levelString);
|
||||
}
|
||||
|
||||
if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
levelString = state.GetRequestedLevel("h265") ?? state.GetRequestedLevel("hevc") ?? "120";
|
||||
levelString = EncodingHelper.NormalizeTranscodingLevel(state, levelString);
|
||||
}
|
||||
}
|
||||
|
||||
if (int.TryParse(levelString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedLevel))
|
||||
{
|
||||
return parsedLevel;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the H.26X 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>
|
||||
private string GetOutputVideoCodecProfile(StreamState state, string codec)
|
||||
{
|
||||
string profileString = string.Empty;
|
||||
if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
|
||||
&& !string.IsNullOrEmpty(state.VideoStream.Profile))
|
||||
{
|
||||
profileString = state.VideoStream.Profile;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(codec))
|
||||
{
|
||||
profileString = state.GetRequestedProfiles(codec).FirstOrDefault();
|
||||
if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
profileString = profileString ?? "high";
|
||||
}
|
||||
|
||||
if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
profileString = profileString ?? "main";
|
||||
}
|
||||
}
|
||||
|
||||
return profileString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a formatted string of the output audio codec, for use in the CODECS field.
|
||||
/// </summary>
|
||||
|
@ -463,6 +597,16 @@ namespace Jellyfin.Api.Helpers
|
|||
return HlsCodecStringHelpers.GetEAC3String();
|
||||
}
|
||||
|
||||
if (string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return HlsCodecStringHelpers.GetFLACString();
|
||||
}
|
||||
|
||||
if (string.Equals(state.ActualOutputAudioCodec, "alac", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return HlsCodecStringHelpers.GetALACString();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
|
@ -487,15 +631,14 @@ namespace Jellyfin.Api.Helpers
|
|||
|
||||
if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string profile = state.GetRequestedProfiles("h264").FirstOrDefault();
|
||||
string profile = GetOutputVideoCodecProfile(state, "h264");
|
||||
return HlsCodecStringHelpers.GetH264String(profile, level);
|
||||
}
|
||||
|
||||
if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string profile = state.GetRequestedProfiles("h265").FirstOrDefault();
|
||||
|
||||
string profile = GetOutputVideoCodecProfile(state, "hevc");
|
||||
return HlsCodecStringHelpers.GetH265String(profile, level);
|
||||
}
|
||||
|
||||
|
@ -539,12 +682,29 @@ namespace Jellyfin.Api.Helpers
|
|||
return variation;
|
||||
}
|
||||
|
||||
private string ReplaceBitrate(string url, int oldValue, int newValue)
|
||||
private string ReplaceVideoBitrate(string url, int oldValue, int newValue)
|
||||
{
|
||||
return url.Replace(
|
||||
"videobitrate=" + oldValue.ToString(CultureInfo.InvariantCulture),
|
||||
"videobitrate=" + newValue.ToString(CultureInfo.InvariantCulture),
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private string ReplaceProfile(string url, string codec, string oldValue, string newValue)
|
||||
{
|
||||
return url.Replace(
|
||||
codec + "-profile=" + oldValue.ToString(),
|
||||
codec + "-profile=" + newValue.ToString(),
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private string ReplacePlaylistCodecsField(StringBuilder playlist, StringBuilder oldValue, StringBuilder newValue)
|
||||
{
|
||||
var oldPlaylist = playlist.ToString();
|
||||
return oldPlaylist.Replace(
|
||||
oldValue.ToString(),
|
||||
newValue.ToString(),
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,20 +85,21 @@ namespace Jellyfin.Api.Helpers
|
|||
// The h265 syntax is a bit of a mystery at the time this comment was written.
|
||||
// This is what I've found through various sources:
|
||||
// FORMAT: [codecTag].[profile].[constraint?].L[level * 30].[UNKNOWN]
|
||||
StringBuilder result = new StringBuilder("hev1", 16);
|
||||
StringBuilder result = new StringBuilder("hvc1", 16);
|
||||
|
||||
if (string.Equals(profile, "main10", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(profile, "main10", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(profile, "main 10", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result.Append(".2.6");
|
||||
result.Append(".2.4");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default to main if profile is invalid
|
||||
result.Append(".1.6");
|
||||
result.Append(".1.4");
|
||||
}
|
||||
|
||||
result.Append(".L")
|
||||
.Append(level * 3)
|
||||
.Append(level)
|
||||
.Append(".B0");
|
||||
|
||||
return result.ToString();
|
||||
|
@ -121,5 +122,23 @@ namespace Jellyfin.Api.Helpers
|
|||
{
|
||||
return "mp4a.a6";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an FLAC codec string.
|
||||
/// </summary>
|
||||
/// <returns>FLAC codec string.</returns>
|
||||
public static string GetFLACString()
|
||||
{
|
||||
return "fLaC";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an ALAC codec string.
|
||||
/// </summary>
|
||||
/// <returns>ALAC codec string.</returns>
|
||||
public static string GetALACString()
|
||||
{
|
||||
return "alac";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
@ -23,7 +24,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
{
|
||||
public class EncodingHelper
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
private static readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
@ -654,16 +655,26 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
return string.Empty;
|
||||
}
|
||||
|
||||
public string NormalizeTranscodingLevel(string videoCodec, string level)
|
||||
public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
|
||||
{
|
||||
// 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)
|
||||
&& requestLevel > 41
|
||||
&& (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)))
|
||||
if (double.TryParse(level, NumberStyles.Any, _usCulture, out double requestLevel))
|
||||
{
|
||||
return "41";
|
||||
if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (requestLevel >= 150)
|
||||
{
|
||||
return "150";
|
||||
}
|
||||
}
|
||||
else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Clients may direct play higher than level 41, but there's no reason to transcode higher
|
||||
if (requestLevel >= 41)
|
||||
{
|
||||
return "41";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return level;
|
||||
|
@ -809,7 +820,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
param += " -crf " + defaultCrf;
|
||||
}
|
||||
}
|
||||
else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)) // h264 (h264_qsv)
|
||||
else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv)
|
||||
|| string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_qsv)
|
||||
{
|
||||
string[] valid_h264_qsv = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" };
|
||||
|
||||
|
@ -825,8 +837,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
param += " -look_ahead 0";
|
||||
}
|
||||
else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
|
||||
|| string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase))
|
||||
|| string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc)
|
||||
{
|
||||
// following preset will be deprecated in ffmpeg 4.4, use p1~p7 instead
|
||||
switch (encodingOptions.EncoderPreset)
|
||||
{
|
||||
case "veryslow":
|
||||
|
@ -856,8 +869,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
break;
|
||||
}
|
||||
}
|
||||
else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
|
||||
else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf)
|
||||
|| string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_amf)
|
||||
{
|
||||
switch (encodingOptions.EncoderPreset)
|
||||
{
|
||||
|
@ -896,6 +909,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
// Enhance workload when tone mapping with AMF on some APUs
|
||||
param += " -preanalysis true";
|
||||
}
|
||||
|
||||
if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
param += " -header_insertion_mode gop -gops_per_idr 1";
|
||||
}
|
||||
}
|
||||
else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // webm
|
||||
{
|
||||
|
@ -945,10 +963,24 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
}
|
||||
|
||||
var targetVideoCodec = state.ActualOutputVideoCodec;
|
||||
if (string.Equals(targetVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(targetVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
targetVideoCodec = "hevc";
|
||||
}
|
||||
|
||||
var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault();
|
||||
profile = Regex.Replace(profile, @"\s+", String.Empty);
|
||||
|
||||
// vaapi does not support Baseline profile, force Constrained Baseline in this case,
|
||||
// only libx264 support encoding H264 High 10 Profile, otherwise force High Profile
|
||||
if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
|
||||
&& profile != null
|
||||
&& profile.IndexOf("high 10", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
profile = "high";
|
||||
}
|
||||
|
||||
// h264_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
|
||||
|
@ -957,6 +989,24 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
profile = "constrained_baseline";
|
||||
}
|
||||
|
||||
// libx264, h264_qsv and h264_nvenc does not support Constrained Baseline profile, force Baseline in this case
|
||||
if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
|
||||
&& profile != null
|
||||
&& profile.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
profile = "baseline";
|
||||
}
|
||||
|
||||
// Currently hevc_amf only support encoding HEVC Main Profile, otherwise force Main Profile
|
||||
if (!string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
|
||||
&& profile != null
|
||||
&& profile.IndexOf("main 10", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
profile = "main";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profile))
|
||||
{
|
||||
if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)
|
||||
|
@ -971,55 +1021,35 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
|
||||
if (!string.IsNullOrEmpty(level))
|
||||
{
|
||||
level = NormalizeTranscodingLevel(state.OutputVideoCodec, level);
|
||||
level = NormalizeTranscodingLevel(state, level);
|
||||
|
||||
// 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
|
||||
// libx264, QSV, AMF, VAAPI can adjust the given level to match the output
|
||||
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
|
||||
|| string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
switch (level)
|
||||
param += " -level " + level;
|
||||
}
|
||||
else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// hevc_qsv use -level 51 instead of -level 153
|
||||
if (double.TryParse(level, NumberStyles.Any, _usCulture, out double hevcLevel))
|
||||
{
|
||||
case "30":
|
||||
param += " -level 3.0";
|
||||
break;
|
||||
case "31":
|
||||
param += " -level 3.1";
|
||||
break;
|
||||
case "32":
|
||||
param += " -level 3.2";
|
||||
break;
|
||||
case "40":
|
||||
param += " -level 4.0";
|
||||
break;
|
||||
case "41":
|
||||
param += " -level 4.1";
|
||||
break;
|
||||
case "42":
|
||||
param += " -level 4.2";
|
||||
break;
|
||||
case "50":
|
||||
param += " -level 5.0";
|
||||
break;
|
||||
case "51":
|
||||
param += " -level 5.1";
|
||||
break;
|
||||
case "52":
|
||||
param += " -level 5.2";
|
||||
break;
|
||||
default:
|
||||
param += " -level " + level;
|
||||
break;
|
||||
param += " -level " + hevcLevel / 3;
|
||||
}
|
||||
}
|
||||
else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
param += " -level " + level;
|
||||
}
|
||||
else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// nvenc doesn't decode with param -level set ?!
|
||||
// TODO:
|
||||
// level option may cause NVENC to fail.
|
||||
// NVENC cannot adjust the given level, just throw an error.
|
||||
}
|
||||
else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase))
|
||||
else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)
|
||||
|| !string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
param += " -level " + level;
|
||||
}
|
||||
|
@ -1032,7 +1062,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
|
||||
if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// todo
|
||||
// libx265 only accept level option in -x265-params
|
||||
// level option may cause libx265 to fail
|
||||
// libx265 cannot adjust the given level, just throw an error
|
||||
// TODO: set fine tuned params
|
||||
param += " -x265-params:0 no-info=1";
|
||||
}
|
||||
|
||||
if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)
|
||||
|
@ -1040,13 +1074,19 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
&& !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
|
||||
&& !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
param = "-pix_fmt yuv420p " + param;
|
||||
}
|
||||
|
||||
if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase))
|
||||
|| string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var videoStream = state.VideoStream;
|
||||
var isColorDepth10 = IsColorDepth10(state);
|
||||
|
@ -1708,7 +1748,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
}
|
||||
|
||||
// For QSV, feed it into hardware encoder now
|
||||
if (isLinux && string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
||||
if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
videoSizeParam += ",hwupload=extra_hw_frames=64";
|
||||
}
|
||||
|
@ -1729,7 +1770,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
: " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"";
|
||||
|
||||
// When the input may or may not be hardware VAAPI decodable
|
||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(outputVideoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
/*
|
||||
[base]: HW scaling video to OutputSize
|
||||
|
@ -1741,7 +1783,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
|
||||
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
|
||||
else if (_mediaEncoder.SupportsHwaccel("vaapi") && videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1
|
||||
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
|
||||
&& (string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(outputVideoCodec, "libx265", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
/*
|
||||
[base]: SW scaling video to OutputSize
|
||||
|
@ -1750,7 +1793,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
*/
|
||||
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"";
|
||||
}
|
||||
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
||||
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
/*
|
||||
QSV in FFMpeg can now setup hardware overlay for transcodes.
|
||||
|
@ -1776,7 +1820,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
videoSizeParam);
|
||||
}
|
||||
|
||||
private (int? width, int? height) GetFixedOutputSize(
|
||||
public static (int? width, int? height) GetFixedOutputSize(
|
||||
int? videoWidth,
|
||||
int? videoHeight,
|
||||
int? requestedWidth,
|
||||
|
@ -1836,7 +1880,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
requestedMaxHeight);
|
||||
|
||||
if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
||||
|| string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
|
||||
&& width.HasValue
|
||||
&& height.HasValue)
|
||||
{
|
||||
|
@ -1845,7 +1891,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
// output dimensions. Output dimensions are guaranteed to be even.
|
||||
var outputWidth = width.Value;
|
||||
var outputHeight = height.Value;
|
||||
var qsv_or_vaapi = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase);
|
||||
var qsv_or_vaapi = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase);
|
||||
var isDeintEnabled = state.DeInterlace("h264", true)
|
||||
|| state.DeInterlace("avc", true)
|
||||
|| state.DeInterlace("h265", true)
|
||||
|
@ -2107,10 +2154,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
var isVaapiHevcEncoder = outputVideoCodec.IndexOf("hevc_vaapi", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
var isQsvHevcEncoder = outputVideoCodec.IndexOf("hevc_qsv", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
var isNvdecH264Decoder = videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
var isNvdecHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
var isLibX265Encoder = outputVideoCodec.IndexOf("libx265", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
var isColorDepth10 = IsColorDepth10(state);
|
||||
|
||||
|
@ -2185,6 +2235,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
filters.Add("hwdownload");
|
||||
|
||||
if (isLibX264Encoder
|
||||
|| isLibX265Encoder
|
||||
|| hasGraphicalSubs
|
||||
|| (isNvdecHevcDecoder && isDeinterlaceHevc)
|
||||
|| (!isNvdecHevcDecoder && isDeinterlaceH264 || isDeinterlaceHevc))
|
||||
|
@ -2195,20 +2246,20 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
}
|
||||
|
||||
// When the input may or may not be hardware VAAPI decodable
|
||||
if (isVaapiH264Encoder)
|
||||
if (isVaapiH264Encoder || isVaapiHevcEncoder)
|
||||
{
|
||||
filters.Add("format=nv12|vaapi");
|
||||
filters.Add("hwupload");
|
||||
}
|
||||
|
||||
// When burning in graphical subtitles using overlay_qsv, upload videostream to the same qsv context
|
||||
else if (isLinux && hasGraphicalSubs && isQsvH264Encoder)
|
||||
else if (isLinux && hasGraphicalSubs && (isQsvH264Encoder || isQsvHevcEncoder))
|
||||
{
|
||||
filters.Add("hwupload=extra_hw_frames=64");
|
||||
}
|
||||
|
||||
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
|
||||
else if (IsVaapiSupported(state) && isVaapiDecoder && isLibX264Encoder)
|
||||
else if (IsVaapiSupported(state) && isVaapiDecoder && (isLibX264Encoder || isLibX265Encoder))
|
||||
{
|
||||
var codec = videoStream.Codec.ToLowerInvariant();
|
||||
|
||||
|
@ -2250,7 +2301,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
// Add software deinterlace filter before scaling filter
|
||||
if ((isDeinterlaceH264 || isDeinterlaceHevc)
|
||||
&& !isVaapiH264Encoder
|
||||
&& !isVaapiHevcEncoder
|
||||
&& !isQsvH264Encoder
|
||||
&& !isQsvHevcEncoder
|
||||
&& !isNvdecH264Decoder)
|
||||
{
|
||||
if (string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase))
|
||||
|
@ -2289,7 +2342,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
}
|
||||
|
||||
// Add parameters to use VAAPI with burn-in text subtitles (GH issue #642)
|
||||
if (isVaapiH264Encoder)
|
||||
if (isVaapiH264Encoder || isVaapiHevcEncoder)
|
||||
{
|
||||
if (hasTextSubs)
|
||||
{
|
||||
|
|
|
@ -15,7 +15,6 @@ namespace MediaBrowser.Model.Dlna
|
|||
new ResolutionConfiguration(720, 950000),
|
||||
new ResolutionConfiguration(1280, 2500000),
|
||||
new ResolutionConfiguration(1920, 4000000),
|
||||
new ResolutionConfiguration(2560, 8000000),
|
||||
new ResolutionConfiguration(3840, 35000000)
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user