mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
Add audio ranking for transcoding profiles (#12546)
This commit is contained in:
parent
43861f0ce1
commit
3da081ba86
@ -751,8 +751,9 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
{
|
{
|
||||||
// Can't direct play, find the transcoding profile
|
// Can't direct play, find the transcoding profile
|
||||||
// If we do this for direct-stream we will overwrite the info
|
// If we do this for direct-stream we will overwrite the info
|
||||||
var transcodingProfile = GetVideoTranscodeProfile(item, options, videoStream, audioStream, candidateAudioStreams, subtitleStream, playlistItem);
|
var (transcodingProfile, playMethod) = GetVideoTranscodeProfile(item, options, videoStream, audioStream, candidateAudioStreams, subtitleStream, playlistItem);
|
||||||
if (transcodingProfile is not null)
|
|
||||||
|
if (transcodingProfile is not null && playMethod.HasValue)
|
||||||
{
|
{
|
||||||
SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile);
|
SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile);
|
||||||
|
|
||||||
@ -790,7 +791,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return playlistItem;
|
return playlistItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TranscodingProfile? GetVideoTranscodeProfile(
|
private (TranscodingProfile? Profile, PlayMethod? PlayMethod) GetVideoTranscodeProfile(
|
||||||
MediaSourceInfo item,
|
MediaSourceInfo item,
|
||||||
MediaOptions options,
|
MediaOptions options,
|
||||||
MediaStream? videoStream,
|
MediaStream? videoStream,
|
||||||
@ -801,7 +802,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
{
|
{
|
||||||
if (!(item.SupportsTranscoding || item.SupportsDirectStream))
|
if (!(item.SupportsTranscoding || item.SupportsDirectStream))
|
||||||
{
|
{
|
||||||
return null;
|
return (null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
var transcodingProfiles = options.Profile.TranscodingProfiles
|
var transcodingProfiles = options.Profile.TranscodingProfiles
|
||||||
@ -812,41 +813,78 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
transcodingProfiles = transcodingProfiles.Where(i => string.Equals(i.Container, "ts", StringComparison.OrdinalIgnoreCase));
|
transcodingProfiles = transcodingProfiles.Where(i => string.Equals(i.Container, "ts", StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.AllowVideoStreamCopy)
|
var videoCodec = videoStream?.Codec;
|
||||||
{
|
float videoFramerate = videoStream?.ReferenceFrameRate ?? 0;
|
||||||
// prefer direct copy profile
|
TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : item.Timestamp;
|
||||||
float videoFramerate = videoStream?.ReferenceFrameRate ?? 0;
|
int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
|
||||||
TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : item.Timestamp;
|
int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
|
||||||
int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
|
|
||||||
int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
|
|
||||||
|
|
||||||
transcodingProfiles = transcodingProfiles.ToLookup(transcodingProfile =>
|
var audioCodec = audioStream?.Codec;
|
||||||
|
var audioProfile = audioStream?.Profile;
|
||||||
|
var audioChannels = audioStream?.Channels;
|
||||||
|
var audioBitrate = audioStream?.BitRate;
|
||||||
|
var audioSampleRate = audioStream?.SampleRate;
|
||||||
|
var audioBitDepth = audioStream?.BitDepth;
|
||||||
|
|
||||||
|
var analyzedProfiles = transcodingProfiles
|
||||||
|
.Select(transcodingProfile =>
|
||||||
{
|
{
|
||||||
var videoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec);
|
var rank = (Video: 3, Audio: 3);
|
||||||
|
|
||||||
if (ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream?.Codec))
|
var container = transcodingProfile.Container;
|
||||||
|
|
||||||
|
if (options.AllowVideoStreamCopy)
|
||||||
{
|
{
|
||||||
var videoCodec = videoStream?.Codec;
|
var videoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec);
|
||||||
var container = transcodingProfile.Container;
|
|
||||||
var appliedVideoConditions = options.Profile.CodecProfiles
|
|
||||||
.Where(i => i.Type == CodecType.Video &&
|
|
||||||
i.ContainsAnyCodec(videoCodec, container) &&
|
|
||||||
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)))
|
|
||||||
.Select(i =>
|
|
||||||
i.Conditions.All(condition => ConditionProcessor.IsVideoConditionSatisfied(condition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)));
|
|
||||||
|
|
||||||
// An empty appliedVideoConditions means that the codec has no conditions for the current video stream
|
if (ContainerProfile.ContainsContainer(videoCodecs, videoCodec))
|
||||||
var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied);
|
{
|
||||||
return conditionsSatisfied ? 1 : 2;
|
var appliedVideoConditions = options.Profile.CodecProfiles
|
||||||
|
.Where(i => i.Type == CodecType.Video &&
|
||||||
|
i.ContainsAnyCodec(videoCodec, container) &&
|
||||||
|
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)))
|
||||||
|
.Select(i =>
|
||||||
|
i.Conditions.All(condition => ConditionProcessor.IsVideoConditionSatisfied(condition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)));
|
||||||
|
|
||||||
|
// An empty appliedVideoConditions means that the codec has no conditions for the current video stream
|
||||||
|
var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied);
|
||||||
|
rank.Video = conditionsSatisfied ? 1 : 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 3;
|
if (options.AllowAudioStreamCopy)
|
||||||
})
|
{
|
||||||
.OrderBy(lookup => lookup.Key)
|
var audioCodecs = ContainerProfile.SplitValue(transcodingProfile.AudioCodec);
|
||||||
.SelectMany(lookup => lookup);
|
|
||||||
}
|
|
||||||
|
|
||||||
return transcodingProfiles.FirstOrDefault();
|
if (ContainerProfile.ContainsContainer(audioCodecs, audioCodec))
|
||||||
|
{
|
||||||
|
var appliedVideoConditions = options.Profile.CodecProfiles
|
||||||
|
.Where(i => i.Type == CodecType.VideoAudio &&
|
||||||
|
i.ContainsAnyCodec(audioCodec, container) &&
|
||||||
|
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, false)))
|
||||||
|
.Select(i =>
|
||||||
|
i.Conditions.All(condition => ConditionProcessor.IsVideoAudioConditionSatisfied(condition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, false)));
|
||||||
|
|
||||||
|
// An empty appliedVideoConditions means that the codec has no conditions for the current audio stream
|
||||||
|
var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied);
|
||||||
|
rank.Audio = conditionsSatisfied ? 1 : 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayMethod playMethod = PlayMethod.Transcode;
|
||||||
|
|
||||||
|
if (rank.Video == 1)
|
||||||
|
{
|
||||||
|
playMethod = PlayMethod.DirectStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Profile: transcodingProfile, PlayMethod: playMethod, Rank: rank);
|
||||||
|
})
|
||||||
|
.OrderBy(analysis => analysis.Rank);
|
||||||
|
|
||||||
|
var profileMatch = analyzedProfiles.FirstOrDefault();
|
||||||
|
|
||||||
|
return (profileMatch.Profile, profileMatch.PlayMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildStreamVideoItem(
|
private void BuildStreamVideoItem(
|
||||||
|
@ -309,6 +309,9 @@ namespace Jellyfin.Model.Tests
|
|||||||
[InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")]
|
[InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")]
|
||||||
[InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")]
|
[InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")]
|
||||||
[InlineData("Tizen4-4K-5.1", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")]
|
[InlineData("Tizen4-4K-5.1", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")]
|
||||||
|
// TranscodeMedia
|
||||||
|
[InlineData("TranscodeMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Remux", "HLS.mp4")]
|
||||||
|
[InlineData("TranscodeMedia", "mp4-h264-ac3-aac-mp3-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Remux", "HLS.ts")]
|
||||||
public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = default, string transcodeMode = "DirectStream", string transcodeProtocol = "")
|
public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = default, string transcodeMode = "DirectStream", string transcodeProtocol = "")
|
||||||
{
|
{
|
||||||
var options = await GetMediaOptions(deviceName, mediaSource);
|
var options = await GetMediaOptions(deviceName, mediaSource);
|
||||||
|
@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"Id": "a766d122b58e45d9492d17af77748bf5",
|
||||||
|
"Path": "/Media/MyVideo-720p.mp4",
|
||||||
|
"Container": "mov,mp4,m4a,3gp,3g2,mj2",
|
||||||
|
"Size": 835317696,
|
||||||
|
"Name": "MyVideo-720p",
|
||||||
|
"ETag": "579a34c6d5dfb21d81539a51220b6a23",
|
||||||
|
"RunTimeTicks": 25801230336,
|
||||||
|
"SupportsTranscoding": true,
|
||||||
|
"SupportsDirectStream": true,
|
||||||
|
"SupportsDirectPlay": true,
|
||||||
|
"SupportsProbing": true,
|
||||||
|
"MediaStreams": [
|
||||||
|
{
|
||||||
|
"Codec": "h264",
|
||||||
|
"CodecTag": "avc1",
|
||||||
|
"Language": "eng",
|
||||||
|
"TimeBase": "1/11988",
|
||||||
|
"VideoRange": "SDR",
|
||||||
|
"DisplayTitle": "720p H264 SDR",
|
||||||
|
"NalLengthSize": "0",
|
||||||
|
"BitRate": 2032876,
|
||||||
|
"BitDepth": 8,
|
||||||
|
"RefFrames": 1,
|
||||||
|
"IsDefault": true,
|
||||||
|
"Height": 720,
|
||||||
|
"Width": 1280,
|
||||||
|
"AverageFrameRate": 23.976,
|
||||||
|
"RealFrameRate": 23.976,
|
||||||
|
"Profile": "High",
|
||||||
|
"Type": 1,
|
||||||
|
"AspectRatio": "16:9",
|
||||||
|
"PixelFormat": "yuv420p",
|
||||||
|
"Level": 41
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "ac3",
|
||||||
|
"CodecTag": "ac-3",
|
||||||
|
"Language": "eng",
|
||||||
|
"TimeBase": "1/48000",
|
||||||
|
"DisplayTitle": "En - Dolby Digital - 5.1 - Default",
|
||||||
|
"ChannelLayout": "5.1",
|
||||||
|
"BitRate": 384000,
|
||||||
|
"Channels": 6,
|
||||||
|
"SampleRate": 48000,
|
||||||
|
"IsDefault": true,
|
||||||
|
"Index": 1,
|
||||||
|
"Score": 202
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "aac",
|
||||||
|
"CodecTag": "mp4a",
|
||||||
|
"Language": "eng",
|
||||||
|
"TimeBase": "1/48000",
|
||||||
|
"DisplayTitle": "En - AAC - Stereo",
|
||||||
|
"ChannelLayout": "stereo",
|
||||||
|
"BitRate": 164741,
|
||||||
|
"Channels": 2,
|
||||||
|
"SampleRate": 48000,
|
||||||
|
"IsDefault": false,
|
||||||
|
"Profile": "LC",
|
||||||
|
"Index": 2,
|
||||||
|
"Score": 203
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "mp3",
|
||||||
|
"Language": "eng",
|
||||||
|
"TimeBase": "1/48000",
|
||||||
|
"DisplayTitle": "En - MP3 - Stereo",
|
||||||
|
"ChannelLayout": "stereo",
|
||||||
|
"BitRate": 164741,
|
||||||
|
"Channels": 2,
|
||||||
|
"SampleRate": 48000,
|
||||||
|
"IsDefault": false,
|
||||||
|
"Index": 3,
|
||||||
|
"Score": 203
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "srt",
|
||||||
|
"Language": "eng",
|
||||||
|
"TimeBase": "1/1000000",
|
||||||
|
"localizedUndefined": "Undefined",
|
||||||
|
"localizedDefault": "Default",
|
||||||
|
"localizedForced": "Forced",
|
||||||
|
"DisplayTitle": "En - Default",
|
||||||
|
"BitRate": 92,
|
||||||
|
"IsDefault": true,
|
||||||
|
"Type": 2,
|
||||||
|
"Index": 4,
|
||||||
|
"Score": 6421,
|
||||||
|
"IsExternal": true,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Bitrate": 2590008,
|
||||||
|
"DefaultAudioStreamIndex": 1,
|
||||||
|
"DefaultSubtitleStreamIndex": 4
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user